usb: gadget: u_serial: reimplement console support
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. Only port ttyGS0 gets to be a console for now. Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl> Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Tested-by: Ladislav Michl <ladis@linux-mips.org> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
This commit is contained in:
parent
daf82bd24e
commit
fe1ea63ad8
|
@ -82,14 +82,12 @@
|
||||||
#define GS_CONSOLE_BUF_SIZE 8192
|
#define GS_CONSOLE_BUF_SIZE 8192
|
||||||
|
|
||||||
/* console info */
|
/* console info */
|
||||||
struct gscons_info {
|
struct gs_console {
|
||||||
struct gs_port *port;
|
struct console console;
|
||||||
struct task_struct *console_thread;
|
struct work_struct work;
|
||||||
struct kfifo con_buf;
|
spinlock_t lock;
|
||||||
/* protect the buf and busy flag */
|
struct usb_request *req;
|
||||||
spinlock_t con_lock;
|
struct kfifo buf;
|
||||||
int req_busy;
|
|
||||||
struct usb_request *console_req;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -101,6 +99,9 @@ struct gs_port {
|
||||||
spinlock_t port_lock; /* guard port_* access */
|
spinlock_t port_lock; /* guard port_* access */
|
||||||
|
|
||||||
struct gserial *port_usb;
|
struct gserial *port_usb;
|
||||||
|
#ifdef CONFIG_U_SERIAL_CONSOLE
|
||||||
|
struct gs_console *console;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool openclose; /* open/close in progress */
|
bool openclose; /* open/close in progress */
|
||||||
u8 port_num;
|
u8 port_num;
|
||||||
|
@ -889,36 +890,9 @@ static struct tty_driver *gs_tty_driver;
|
||||||
|
|
||||||
#ifdef CONFIG_U_SERIAL_CONSOLE
|
#ifdef CONFIG_U_SERIAL_CONSOLE
|
||||||
|
|
||||||
static struct gscons_info gscons_info;
|
static void gs_console_complete_out(struct usb_ep *ep, struct usb_request *req)
|
||||||
static struct console gserial_cons;
|
|
||||||
|
|
||||||
static struct usb_request *gs_request_new(struct usb_ep *ep)
|
|
||||||
{
|
{
|
||||||
struct usb_request *req = usb_ep_alloc_request(ep, GFP_ATOMIC);
|
struct gs_console *cons = req->context;
|
||||||
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;
|
|
||||||
|
|
||||||
switch (req->status) {
|
switch (req->status) {
|
||||||
default:
|
default:
|
||||||
|
@ -927,12 +901,12 @@ static void gs_complete_out(struct usb_ep *ep, struct usb_request *req)
|
||||||
/* fall through */
|
/* fall through */
|
||||||
case 0:
|
case 0:
|
||||||
/* normal completion */
|
/* normal completion */
|
||||||
spin_lock(&info->con_lock);
|
spin_lock(&cons->lock);
|
||||||
info->req_busy = 0;
|
req->length = 0;
|
||||||
spin_unlock(&info->con_lock);
|
schedule_work(&cons->work);
|
||||||
|
spin_unlock(&cons->lock);
|
||||||
wake_up_process(info->console_thread);
|
|
||||||
break;
|
break;
|
||||||
|
case -ECONNRESET:
|
||||||
case -ESHUTDOWN:
|
case -ESHUTDOWN:
|
||||||
/* disconnect */
|
/* disconnect */
|
||||||
pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
|
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 usb_request *req = cons->req;
|
||||||
struct gs_port *port;
|
|
||||||
struct usb_ep *ep;
|
struct usb_ep *ep;
|
||||||
|
size_t size;
|
||||||
|
|
||||||
if (port_num != gserial_cons.index) {
|
if (!req)
|
||||||
pr_err("%s: port num [%d] is not support console\n",
|
return; /* disconnected */
|
||||||
__func__, port_num);
|
|
||||||
return -ENXIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
port = ports[port_num].port;
|
if (req->length)
|
||||||
ep = port->port_usb->in;
|
return; /* busy */
|
||||||
if (!info->console_req) {
|
|
||||||
info->console_req = gs_request_new(ep);
|
|
||||||
if (!info->console_req)
|
|
||||||
return -ENOMEM;
|
|
||||||
info->console_req->complete = gs_complete_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
info->port = port;
|
ep = cons->console.data;
|
||||||
spin_lock(&info->con_lock);
|
size = kfifo_out(&cons->buf, req->buf, ep->maxpacket);
|
||||||
info->req_busy = 0;
|
if (!size)
|
||||||
spin_unlock(&info->con_lock);
|
return;
|
||||||
pr_vdebug("port[%d] console connect!\n", port_num);
|
|
||||||
return 0;
|
req->length = size;
|
||||||
|
if (usb_ep_queue(ep, req, GFP_ATOMIC))
|
||||||
|
req->length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gs_console_disconnect(struct usb_ep *ep)
|
static void gs_console_work(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct gscons_info *info = &gscons_info;
|
struct gs_console *cons = container_of(work, struct gs_console, work);
|
||||||
struct usb_request *req = info->console_req;
|
|
||||||
|
|
||||||
gs_request_free(req, ep);
|
spin_lock_irq(&cons->lock);
|
||||||
info->console_req = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int gs_console_thread(void *data)
|
__gs_console_push(cons);
|
||||||
{
|
|
||||||
struct gscons_info *info = &gscons_info;
|
|
||||||
struct gs_port *port;
|
|
||||||
struct usb_request *req;
|
|
||||||
struct usb_ep *ep;
|
|
||||||
int xfer, ret, count, size;
|
|
||||||
|
|
||||||
do {
|
spin_unlock_irq(&cons->lock);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int gs_console_setup(struct console *co, char *options)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gs_console_write(struct console *co,
|
static void gs_console_write(struct console *co,
|
||||||
const char *buf, unsigned count)
|
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;
|
unsigned long flags;
|
||||||
|
|
||||||
spin_lock_irqsave(&info->con_lock, flags);
|
spin_lock_irqsave(&cons->lock, flags);
|
||||||
kfifo_in(&info->con_buf, buf, count);
|
|
||||||
spin_unlock_irqrestore(&info->con_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)
|
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;
|
*index = co->index;
|
||||||
return *p;
|
return gs_tty_driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct console gserial_cons = {
|
static int gs_console_connect(struct gs_port *port)
|
||||||
.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)
|
|
||||||
{
|
{
|
||||||
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 gserial_console_exit(void)
|
static void gs_console_disconnect(struct gs_port *port)
|
||||||
{
|
{
|
||||||
struct gscons_info *info = &gscons_info;
|
struct gs_console *cons = port->console;
|
||||||
|
struct usb_request *req;
|
||||||
|
struct usb_ep *ep;
|
||||||
|
|
||||||
unregister_console(&gserial_cons);
|
if (!cons)
|
||||||
if (!IS_ERR_OR_NULL(info->console_thread))
|
return;
|
||||||
kthread_stop(info->console_thread);
|
|
||||||
kfifo_free(&info->con_buf);
|
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 int gs_console_init(struct gs_port *port)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
cancel_work_sync(&cons->work);
|
||||||
|
kfifo_free(&cons->buf);
|
||||||
|
kfree(cons);
|
||||||
|
port->console = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static int gs_console_connect(int port_num)
|
static int gs_console_connect(struct gs_port *port)
|
||||||
{
|
{
|
||||||
return 0;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
port = ports[port_num].port;
|
port = ports[port_num].port;
|
||||||
|
gs_console_exit(port);
|
||||||
ports[port_num].port = NULL;
|
ports[port_num].port = NULL;
|
||||||
mutex_unlock(&ports[port_num].lock);
|
mutex_unlock(&ports[port_num].lock);
|
||||||
|
|
||||||
gserial_free_port(port);
|
gserial_free_port(port);
|
||||||
tty_unregister_device(gs_tty_driver, port_num);
|
tty_unregister_device(gs_tty_driver, port_num);
|
||||||
gserial_console_exit();
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(gserial_free_line);
|
EXPORT_SYMBOL_GPL(gserial_free_line);
|
||||||
|
|
||||||
int gserial_alloc_line(unsigned char *line_num)
|
int gserial_alloc_line(unsigned char *line_num)
|
||||||
{
|
{
|
||||||
struct usb_cdc_line_coding coding;
|
struct usb_cdc_line_coding coding;
|
||||||
|
struct gs_port *port;
|
||||||
struct device *tty_dev;
|
struct device *tty_dev;
|
||||||
int ret;
|
int ret;
|
||||||
int port_num;
|
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* */
|
/* ... 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);
|
gs_tty_driver, port_num, NULL);
|
||||||
if (IS_ERR(tty_dev)) {
|
if (IS_ERR(tty_dev)) {
|
||||||
struct gs_port *port;
|
|
||||||
pr_err("%s: failed to register tty for port %d, err %ld\n",
|
pr_err("%s: failed to register tty for port %d, err %ld\n",
|
||||||
__func__, port_num, PTR_ERR(tty_dev));
|
__func__, port_num, PTR_ERR(tty_dev));
|
||||||
|
|
||||||
ret = PTR_ERR(tty_dev);
|
ret = PTR_ERR(tty_dev);
|
||||||
mutex_lock(&ports[port_num].lock);
|
mutex_lock(&ports[port_num].lock);
|
||||||
port = ports[port_num].port;
|
|
||||||
ports[port_num].port = NULL;
|
ports[port_num].port = NULL;
|
||||||
mutex_unlock(&ports[port_num].lock);
|
mutex_unlock(&ports[port_num].lock);
|
||||||
gserial_free_port(port);
|
gserial_free_port(port);
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
*line_num = port_num;
|
*line_num = port_num;
|
||||||
gserial_console_init();
|
|
||||||
|
if (!port_num)
|
||||||
|
gs_console_init(port);
|
||||||
err:
|
err:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1329,7 +1305,7 @@ int gserial_connect(struct gserial *gser, u8 port_num)
|
||||||
gser->disconnect(gser);
|
gser->disconnect(gser);
|
||||||
}
|
}
|
||||||
|
|
||||||
status = gs_console_connect(port_num);
|
status = gs_console_connect(port);
|
||||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
@ -1361,6 +1337,8 @@ void gserial_disconnect(struct gserial *gser)
|
||||||
/* tell the TTY glue not to do I/O here any more */
|
/* tell the TTY glue not to do I/O here any more */
|
||||||
spin_lock_irqsave(&port->port_lock, flags);
|
spin_lock_irqsave(&port->port_lock, flags);
|
||||||
|
|
||||||
|
gs_console_disconnect(port);
|
||||||
|
|
||||||
/* REVISIT as above: how best to track this? */
|
/* REVISIT as above: how best to track this? */
|
||||||
port->port_line_coding = gser->port_line_coding;
|
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->read_allocated = port->read_started =
|
||||||
port->write_allocated = port->write_started = 0;
|
port->write_allocated = port->write_started = 0;
|
||||||
|
|
||||||
gs_console_disconnect(gser->in);
|
|
||||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(gserial_disconnect);
|
EXPORT_SYMBOL_GPL(gserial_disconnect);
|
||||||
|
|
Loading…
Reference in New Issue