Re: [PATCH v5 2/6] usb: gadget: u_serial: reimplement console support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Michal,

On Mon, 22 Jul 2019 at 23:26, Michał Mirosław <mirq-linux@xxxxxxxxxxxx> wrote:
>
> Rewrite console support to fix a few shortcomings of the old code
> preventing its use with multiple ports. This removes some duplicated
> code and replaces a custom kthread with simpler workqueue item.

Could you elaborate on why changing kthread to a workqueue? The
purpose of using kthread here is considering that the kthead has a
better scheduler response than pooled kworker.

>
> Only port ttyGS0 gets to be a console for now.
>
> Signed-off-by: Michał Mirosław <mirq-linux@xxxxxxxxxxxx>
> Reviewed-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
> Tested-by: Ladislav Michl <ladis@xxxxxxxxxxxxxx>
>
> ---
>   v5: no changes
>   v4: cosmetic change to __gs_console_push()
>   v3: no changes
>   v2: no changes
>
> ---
>  drivers/usb/gadget/function/u_serial.c | 351 ++++++++++++-------------
>  1 file changed, 164 insertions(+), 187 deletions(-)
>
> diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
> index bb1e2e1d0076..94f6999e8262 100644
> --- a/drivers/usb/gadget/function/u_serial.c
> +++ b/drivers/usb/gadget/function/u_serial.c
> @@ -82,14 +82,12 @@
>  #define GS_CONSOLE_BUF_SIZE    8192
>
>  /* console info */
> -struct gscons_info {
> -       struct gs_port          *port;
> -       struct task_struct      *console_thread;
> -       struct kfifo            con_buf;
> -       /* protect the buf and busy flag */
> -       spinlock_t              con_lock;
> -       int                     req_busy;
> -       struct usb_request      *console_req;
> +struct gs_console {
> +       struct console          console;
> +       struct work_struct      work;
> +       spinlock_t              lock;
> +       struct usb_request      *req;
> +       struct kfifo            buf;
>  };
>
>  /*
> @@ -101,6 +99,9 @@ struct gs_port {
>         spinlock_t              port_lock;      /* guard port_* access */
>
>         struct gserial          *port_usb;
> +#ifdef CONFIG_U_SERIAL_CONSOLE
> +       struct gs_console       *console;
> +#endif
>
>         bool                    openclose;      /* open/close in progress */
>         u8                      port_num;
> @@ -889,36 +890,9 @@ static struct tty_driver *gs_tty_driver;
>
>  #ifdef CONFIG_U_SERIAL_CONSOLE
>
> -static struct gscons_info gscons_info;
> -static struct console gserial_cons;
> -
> -static struct usb_request *gs_request_new(struct usb_ep *ep)
> +static void gs_console_complete_out(struct usb_ep *ep, struct usb_request *req)
>  {
> -       struct usb_request *req = usb_ep_alloc_request(ep, GFP_ATOMIC);
> -       if (!req)
> -               return NULL;
> -
> -       req->buf = kmalloc(ep->maxpacket, GFP_ATOMIC);
> -       if (!req->buf) {
> -               usb_ep_free_request(ep, req);
> -               return NULL;
> -       }
> -
> -       return req;
> -}
> -
> -static void gs_request_free(struct usb_request *req, struct usb_ep *ep)
> -{
> -       if (!req)
> -               return;
> -
> -       kfree(req->buf);
> -       usb_ep_free_request(ep, req);
> -}
> -
> -static void gs_complete_out(struct usb_ep *ep, struct usb_request *req)
> -{
> -       struct gscons_info *info = &gscons_info;
> +       struct gs_console *cons = req->context;
>
>         switch (req->status) {
>         default:
> @@ -927,12 +901,12 @@ static void gs_complete_out(struct usb_ep *ep, struct usb_request *req)
>                 /* fall through */
>         case 0:
>                 /* normal completion */
> -               spin_lock(&info->con_lock);
> -               info->req_busy = 0;
> -               spin_unlock(&info->con_lock);
> -
> -               wake_up_process(info->console_thread);
> +               spin_lock(&cons->lock);
> +               req->length = 0;
> +               schedule_work(&cons->work);
> +               spin_unlock(&cons->lock);
>                 break;
> +       case -ECONNRESET:
>         case -ESHUTDOWN:
>                 /* disconnect */
>                 pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
> @@ -940,190 +914,190 @@ static void gs_complete_out(struct usb_ep *ep, struct usb_request *req)
>         }
>  }
>
> -static int gs_console_connect(int port_num)
> +static void __gs_console_push(struct gs_console *cons)
>  {
> -       struct gscons_info *info = &gscons_info;
> -       struct gs_port *port;
> +       struct usb_request *req = cons->req;
>         struct usb_ep *ep;
> +       size_t size;
>
> -       if (port_num != gserial_cons.index) {
> -               pr_err("%s: port num [%d] is not support console\n",
> -                      __func__, port_num);
> -               return -ENXIO;
> -       }
> +       if (!req)
> +               return; /* disconnected */
>
> -       port = ports[port_num].port;
> -       ep = port->port_usb->in;
> -       if (!info->console_req) {
> -               info->console_req = gs_request_new(ep);
> -               if (!info->console_req)
> -                       return -ENOMEM;
> -               info->console_req->complete = gs_complete_out;
> -       }
> +       if (req->length)
> +               return; /* busy */
>
> -       info->port = port;
> -       spin_lock(&info->con_lock);
> -       info->req_busy = 0;
> -       spin_unlock(&info->con_lock);
> -       pr_vdebug("port[%d] console connect!\n", port_num);
> -       return 0;
> -}
> -
> -static void gs_console_disconnect(struct usb_ep *ep)
> -{
> -       struct gscons_info *info = &gscons_info;
> -       struct usb_request *req = info->console_req;
> -
> -       gs_request_free(req, ep);
> -       info->console_req = NULL;
> -}
> -
> -static int gs_console_thread(void *data)
> -{
> -       struct gscons_info *info = &gscons_info;
> -       struct gs_port *port;
> -       struct usb_request *req;
> -       struct usb_ep *ep;
> -       int xfer, ret, count, size;
> +       ep = cons->console.data;
> +       size = kfifo_out(&cons->buf, req->buf, ep->maxpacket);
> +       if (!size)
> +               return;
>
> -       do {
> -               port = info->port;
> -               set_current_state(TASK_INTERRUPTIBLE);
> -               if (!port || !port->port_usb
> -                   || !port->port_usb->in || !info->console_req)
> -                       goto sched;
> -
> -               req = info->console_req;
> -               ep = port->port_usb->in;
> -
> -               spin_lock_irq(&info->con_lock);
> -               count = kfifo_len(&info->con_buf);
> -               size = ep->maxpacket;
> -
> -               if (count > 0 && !info->req_busy) {
> -                       set_current_state(TASK_RUNNING);
> -                       if (count < size)
> -                               size = count;
> -
> -                       xfer = kfifo_out(&info->con_buf, req->buf, size);
> -                       req->length = xfer;
> -
> -                       spin_unlock(&info->con_lock);
> -                       ret = usb_ep_queue(ep, req, GFP_ATOMIC);
> -                       spin_lock(&info->con_lock);
> -                       if (ret < 0)
> -                               info->req_busy = 0;
> -                       else
> -                               info->req_busy = 1;
> -
> -                       spin_unlock_irq(&info->con_lock);
> -               } else {
> -                       spin_unlock_irq(&info->con_lock);
> -sched:
> -                       if (kthread_should_stop()) {
> -                               set_current_state(TASK_RUNNING);
> -                               break;
> -                       }
> -                       schedule();
> -               }
> -       } while (1);
> -
> -       return 0;
> +       req->length = size;
> +       if (usb_ep_queue(ep, req, GFP_ATOMIC))
> +               req->length = 0;
>  }
>
> -static int gs_console_setup(struct console *co, char *options)
> +static void gs_console_work(struct work_struct *work)
>  {
> -       struct gscons_info *info = &gscons_info;
> -       int status;
> -
> -       info->port = NULL;
> -       info->console_req = NULL;
> -       info->req_busy = 0;
> -       spin_lock_init(&info->con_lock);
> +       struct gs_console *cons = container_of(work, struct gs_console, work);
>
> -       status = kfifo_alloc(&info->con_buf, GS_CONSOLE_BUF_SIZE, GFP_KERNEL);
> -       if (status) {
> -               pr_err("%s: allocate console buffer failed\n", __func__);
> -               return status;
> -       }
> +       spin_lock_irq(&cons->lock);
>
> -       info->console_thread = kthread_create(gs_console_thread,
> -                                             co, "gs_console");
> -       if (IS_ERR(info->console_thread)) {
> -               pr_err("%s: cannot create console thread\n", __func__);
> -               kfifo_free(&info->con_buf);
> -               return PTR_ERR(info->console_thread);
> -       }
> -       wake_up_process(info->console_thread);
> +       __gs_console_push(cons);
>
> -       return 0;
> +       spin_unlock_irq(&cons->lock);
>  }
>
>  static void gs_console_write(struct console *co,
>                              const char *buf, unsigned count)
>  {
> -       struct gscons_info *info = &gscons_info;
> +       struct gs_console *cons = container_of(co, struct gs_console, console);
>         unsigned long flags;
>
> -       spin_lock_irqsave(&info->con_lock, flags);
> -       kfifo_in(&info->con_buf, buf, count);
> -       spin_unlock_irqrestore(&info->con_lock, flags);
> +       spin_lock_irqsave(&cons->lock, flags);
>
> -       wake_up_process(info->console_thread);
> +       kfifo_in(&cons->buf, buf, count);
> +
> +       if (cons->req && !cons->req->length)
> +               schedule_work(&cons->work);
> +
> +       spin_unlock_irqrestore(&cons->lock, flags);
>  }
>
>  static struct tty_driver *gs_console_device(struct console *co, int *index)
>  {
> -       struct tty_driver **p = (struct tty_driver **)co->data;
> -
> -       if (!*p)
> -               return NULL;
> -
>         *index = co->index;
> -       return *p;
> +       return gs_tty_driver;
>  }
>
> -static struct console gserial_cons = {
> -       .name =         "ttyGS",
> -       .write =        gs_console_write,
> -       .device =       gs_console_device,
> -       .setup =        gs_console_setup,
> -       .flags =        CON_PRINTBUFFER,
> -       .index =        -1,
> -       .data =         &gs_tty_driver,
> -};
> -
> -static void gserial_console_init(void)
> +static int gs_console_connect(struct gs_port *port)
>  {
> -       register_console(&gserial_cons);
> +       struct gs_console *cons = port->console;
> +       struct usb_request *req;
> +       struct usb_ep *ep;
> +
> +       if (!cons)
> +               return 0;
> +
> +       ep = port->port_usb->in;
> +       req = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC);
> +       if (!req)
> +               return -ENOMEM;
> +       req->complete = gs_console_complete_out;
> +       req->context = cons;
> +       req->length = 0;
> +
> +       spin_lock(&cons->lock);
> +       cons->req = req;
> +       cons->console.data = ep;
> +       spin_unlock(&cons->lock);
> +
> +       pr_debug("ttyGS%d: console connected!\n", port->port_num);
> +
> +       schedule_work(&cons->work);
> +
> +       return 0;
> +}
> +
> +static void gs_console_disconnect(struct gs_port *port)
> +{
> +       struct gs_console *cons = port->console;
> +       struct usb_request *req;
> +       struct usb_ep *ep;
> +
> +       if (!cons)
> +               return;
> +
> +       spin_lock(&cons->lock);
> +
> +       req = cons->req;
> +       ep = cons->console.data;
> +       cons->req = NULL;
> +
> +       spin_unlock(&cons->lock);
> +
> +       if (!req)
> +               return;
> +
> +       usb_ep_dequeue(ep, req);
> +       gs_free_req(ep, req);
>  }
>
> -static void gserial_console_exit(void)
> +static int gs_console_init(struct gs_port *port)
>  {
> -       struct gscons_info *info = &gscons_info;
> +       struct gs_console *cons;
> +       int err;
> +
> +       if (port->console)
> +               return 0;
> +
> +       cons = kzalloc(sizeof(*port->console), GFP_KERNEL);
> +       if (!cons)
> +               return -ENOMEM;
> +
> +       strcpy(cons->console.name, "ttyGS");
> +       cons->console.write = gs_console_write;
> +       cons->console.device = gs_console_device;
> +       cons->console.flags = CON_PRINTBUFFER;
> +       cons->console.index = port->port_num;
> +
> +       INIT_WORK(&cons->work, gs_console_work);
> +       spin_lock_init(&cons->lock);
> +
> +       err = kfifo_alloc(&cons->buf, GS_CONSOLE_BUF_SIZE, GFP_KERNEL);
> +       if (err) {
> +               pr_err("ttyGS%d: allocate console buffer failed\n", port->port_num);
> +               kfree(cons);
> +               return err;
> +       }
> +
> +       port->console = cons;
> +       register_console(&cons->console);
> +
> +       spin_lock_irq(&port->port_lock);
> +       if (port->port_usb)
> +               gs_console_connect(port);
> +       spin_unlock_irq(&port->port_lock);
> +
> +       return 0;
> +}
> +
> +static void gs_console_exit(struct gs_port *port)
> +{
> +       struct gs_console *cons = port->console;
> +
> +       if (!cons)
> +               return;
> +
> +       unregister_console(&cons->console);
> +
> +       spin_lock_irq(&port->port_lock);
> +       if (cons->req)
> +               gs_console_disconnect(port);
> +       spin_unlock_irq(&port->port_lock);
>
> -       unregister_console(&gserial_cons);
> -       if (!IS_ERR_OR_NULL(info->console_thread))
> -               kthread_stop(info->console_thread);
> -       kfifo_free(&info->con_buf);
> +       cancel_work_sync(&cons->work);
> +       kfifo_free(&cons->buf);
> +       kfree(cons);
> +       port->console = NULL;
>  }
>
>  #else
>
> -static int gs_console_connect(int port_num)
> +static int gs_console_connect(struct gs_port *port)
>  {
>         return 0;
>  }
>
> -static void gs_console_disconnect(struct usb_ep *ep)
> +static void gs_console_disconnect(struct gs_port *port)
>  {
>  }
>
> -static void gserial_console_init(void)
> +static int gs_console_init(struct gs_port *port)
>  {
> +       return -ENOSYS;
>  }
>
> -static void gserial_console_exit(void)
> +static void gs_console_exit(struct gs_port *port)
>  {
>  }
>
> @@ -1197,18 +1171,19 @@ void gserial_free_line(unsigned char port_num)
>                 return;
>         }
>         port = ports[port_num].port;
> +       gs_console_exit(port);
>         ports[port_num].port = NULL;
>         mutex_unlock(&ports[port_num].lock);
>
>         gserial_free_port(port);
>         tty_unregister_device(gs_tty_driver, port_num);
> -       gserial_console_exit();
>  }
>  EXPORT_SYMBOL_GPL(gserial_free_line);
>
>  int gserial_alloc_line(unsigned char *line_num)
>  {
>         struct usb_cdc_line_coding      coding;
> +       struct gs_port                  *port;
>         struct device                   *tty_dev;
>         int                             ret;
>         int                             port_num;
> @@ -1231,23 +1206,24 @@ int gserial_alloc_line(unsigned char *line_num)
>
>         /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
>
> -       tty_dev = tty_port_register_device(&ports[port_num].port->port,
> +       port = ports[port_num].port;
> +       tty_dev = tty_port_register_device(&port->port,
>                         gs_tty_driver, port_num, NULL);
>         if (IS_ERR(tty_dev)) {
> -               struct gs_port  *port;
>                 pr_err("%s: failed to register tty for port %d, err %ld\n",
>                                 __func__, port_num, PTR_ERR(tty_dev));
>
>                 ret = PTR_ERR(tty_dev);
>                 mutex_lock(&ports[port_num].lock);
> -               port = ports[port_num].port;
>                 ports[port_num].port = NULL;
>                 mutex_unlock(&ports[port_num].lock);
>                 gserial_free_port(port);
>                 goto err;
>         }
>         *line_num = port_num;
> -       gserial_console_init();
> +
> +       if (!port_num)
> +               gs_console_init(port);
>  err:
>         return ret;
>  }
> @@ -1329,7 +1305,7 @@ int gserial_connect(struct gserial *gser, u8 port_num)
>                         gser->disconnect(gser);
>         }
>
> -       status = gs_console_connect(port_num);
> +       status = gs_console_connect(port);
>         spin_unlock_irqrestore(&port->port_lock, flags);
>
>         return status;
> @@ -1361,6 +1337,8 @@ void gserial_disconnect(struct gserial *gser)
>         /* tell the TTY glue not to do I/O here any more */
>         spin_lock_irqsave(&port->port_lock, flags);
>
> +       gs_console_disconnect(port);
> +
>         /* REVISIT as above: how best to track this? */
>         port->port_line_coding = gser->port_line_coding;
>
> @@ -1388,7 +1366,6 @@ void gserial_disconnect(struct gserial *gser)
>         port->read_allocated = port->read_started =
>                 port->write_allocated = port->write_started = 0;
>
> -       gs_console_disconnect(gser->in);
>         spin_unlock_irqrestore(&port->port_lock, flags);
>  }
>  EXPORT_SYMBOL_GPL(gserial_disconnect);
> --
> 2.20.1
>


-- 
Baolin Wang
Best Regards




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux