On Thursday 25 November 2021 18:16:15 Loic Poulain wrote: > Add support for CDC ACM using the new UDC and gadget API. This protocol > can be used for serial over USB data transfer and is widely supported > by various OS (GNU/Linux, MS-Windows, OSX...). The usual purpose of > such link is to access device debug console and can be useful for > products not exposing regular UART to the user. > > A default stdio device named 'usbacm' is created, and can be used > to redirect console to USB link over CDC ACM: > > > setenv stdin usbacm; setenv stdout usbacm > > Signed-off-by: Loic Poulain <loic.poul...@linaro.org> > --- > v2: - remove possible infinite recursipe print loop > - Remove acmconsole command, start function along the stdio device > v3: - Use __constant_cpu_to_le16() when possible > - Rename stdio dev to 'usbacm' > - Rename init function to drv_usbacm_init
Hello! Just one question: is in this v3 patch fixed stdout-only configuration, or it still has a bug which you described in email? https://lore.kernel.org/u-boot/CAMZdPi8185pAiO4w2QYMtWGAFJiX=ej_bjw1cusre990wr7...@mail.gmail.com/ Because neither in above change long nor in driver help description nor in driver source code is any comment related to stdout-only device. Note that stdout-only does not have to be too uncommon. For example Nokia N900 has enabled UART, keyboard+lcd and usbtty by default and sometimes I enabled input only from keyboard and looked at usbtty terminal (as stdout-only) if everything is working fine (that every pressed key on keyboard was echoed correctly back to usbtty). > > common/stdio.c | 3 + > drivers/usb/gadget/Kconfig | 9 + > drivers/usb/gadget/Makefile | 1 + > drivers/usb/gadget/f_acm.c | 672 > ++++++++++++++++++++++++++++++++++++++++++++ > include/stdio_dev.h | 1 + > 5 files changed, 686 insertions(+) > create mode 100644 drivers/usb/gadget/f_acm.c > > diff --git a/common/stdio.c b/common/stdio.c > index 4083e4e..e07e4a4 100644 > --- a/common/stdio.c > +++ b/common/stdio.c > @@ -381,6 +381,9 @@ int stdio_add_devices(void) > #ifdef CONFIG_USB_TTY > drv_usbtty_init(); > #endif > +#ifdef CONFIG_USB_FUNCTION_ACM > + drv_usbacm_init (); > +#endif > if (IS_ENABLED(CONFIG_NETCONSOLE)) > drv_nc_init(); > #ifdef CONFIG_JTAG_CONSOLE > diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig > index 327ea86..d81a9c5 100644 > --- a/drivers/usb/gadget/Kconfig > +++ b/drivers/usb/gadget/Kconfig > @@ -182,6 +182,15 @@ config USB_FUNCTION_THOR > Enable Tizen's THOR download protocol support in U-Boot. It > allows downloading images into memory and flash them to target device. > > +config USB_FUNCTION_ACM > + bool "Enable CDC ACM gadget" > + select SYS_STDIO_DEREGISTER > + select CIRCBUF > + help > + ACM serial link. This function can be used to create a stdio device to > + interoperate with MS-Windows hosts or with the Linux-USB "cdc-acm" > + driver. > + > endif # USB_GADGET_DOWNLOAD > > config USB_ETHER > diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile > index f560068..d5d891b 100644 > --- a/drivers/usb/gadget/Makefile > +++ b/drivers/usb/gadget/Makefile > @@ -30,6 +30,7 @@ obj-$(CONFIG_USB_FUNCTION_MASS_STORAGE) += f_mass_storage.o > obj-$(CONFIG_USB_FUNCTION_FASTBOOT) += f_fastboot.o > obj-$(CONFIG_USB_FUNCTION_SDP) += f_sdp.o > obj-$(CONFIG_USB_FUNCTION_ROCKUSB) += f_rockusb.o > +obj-$(CONFIG_USB_FUNCTION_ACM) += f_acm.o > endif > endif > ifdef CONFIG_USB_ETHER > diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c > new file mode 100644 > index 0000000..388f73d > --- /dev/null > +++ b/drivers/usb/gadget/f_acm.c > @@ -0,0 +1,672 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * USB CDC serial (ACM) function driver > + * > + * Copyright (C) 2003 Al Borchers (alborch...@steinerpoint.com) > + * Copyright (C) 2008 by David Brownell > + * Copyright (C) 2008 by Nokia Corporation > + * Copyright (C) 2009 by Samsung Electronics > + * Copyright (c) 2021, Linaro Ltd <loic.poul...@linaro.org> > + */ > + > +#include <circbuf.h> > +#include <common.h> > +#include <console.h> > +#include <errno.h> > +#include <g_dnl.h> > +#include <malloc.h> > +#include <memalign.h> > +#include <stdio_dev.h> > +#include <version.h> > +#include <watchdog.h> > + > +#include <linux/usb/ch9.h> > +#include <linux/usb/gadget.h> > +#include <linux/usb/composite.h> > +#include <linux/usb/cdc.h> > + > +#define REQ_SIZE_MAX 512 > + > +struct f_acm { > + int ctrl_id; > + int data_id; > + > + struct usb_ep *ep_in; > + struct usb_ep *ep_out; > + struct usb_ep *ep_notify; > + > + struct usb_request *req_in; > + struct usb_request *req_out; > + > + bool connected; > + bool tx_on; > + > + circbuf_t rx_buf; > + circbuf_t tx_buf; > + > + struct usb_function usb_function; > + > + struct usb_cdc_line_coding line_coding; > + u16 handshake_bits; > +#define ACM_CTRL_RTS BIT(1) /* unused with full duplex */ > +#define ACM_CTRL_DTR BIT(0) /* host is ready for data r/w */ > + > + int controller_index; > +}; > + > +static struct f_acm *default_acm_function; > + > +static inline struct f_acm *func_to_acm(struct usb_function *f) > +{ > + return container_of(f, struct f_acm, usb_function); > +} > + > +static inline struct f_acm *stdio_to_acm(struct stdio_dev *s) > +{ > + /* stdio dev is cloned on registration, do not use container_of */ > + return s->priv; > +} > + > +static struct usb_interface_assoc_descriptor > +acm_iad_descriptor = { > + .bLength = sizeof(acm_iad_descriptor), > + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, > + .bFirstInterface = 0, > + .bInterfaceCount = 2, // control + data > + .bFunctionClass = USB_CLASS_COMM, > + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, > + .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER, > +}; > + > +static struct usb_interface_descriptor acm_control_intf_desc = { > + .bLength = USB_DT_INTERFACE_SIZE, > + .bDescriptorType = USB_DT_INTERFACE, > + .bNumEndpoints = 1, > + .bInterfaceClass = USB_CLASS_COMM, > + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, > + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, > +}; > + > +static struct usb_interface_descriptor acm_data_intf_desc = { > + .bLength = sizeof(acm_data_intf_desc), > + .bDescriptorType = USB_DT_INTERFACE, > + .bNumEndpoints = 2, > + .bInterfaceClass = USB_CLASS_CDC_DATA, > +}; > + > +static struct usb_cdc_header_desc acm_header_desc = { > + .bLength = sizeof(acm_header_desc), > + .bDescriptorType = USB_DT_CS_INTERFACE, > + .bDescriptorSubType = USB_CDC_HEADER_TYPE, > + .bcdCDC = __constant_cpu_to_le16(0x0110), > +}; > + > +static struct usb_cdc_call_mgmt_descriptor acm_call_mgmt_desc = { > + .bLength = sizeof(acm_call_mgmt_desc), > + .bDescriptorType = USB_DT_CS_INTERFACE, > + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, > + .bmCapabilities = 0, > + .bDataInterface = 0x01, > +}; > + > +static struct usb_cdc_acm_descriptor acm_desc = { > + .bLength = sizeof(acm_desc), > + .bDescriptorType = USB_DT_CS_INTERFACE, > + .bDescriptorSubType = USB_CDC_ACM_TYPE, > + .bmCapabilities = USB_CDC_CAP_LINE, > +}; > + > +static struct usb_cdc_union_desc acm_union_desc = { > + .bLength = sizeof(acm_union_desc), > + .bDescriptorType = USB_DT_CS_INTERFACE, > + .bDescriptorSubType = USB_CDC_UNION_TYPE, > + .bMasterInterface0 = 0x00, > + .bSlaveInterface0 = 0x01, > +}; > + > +static struct usb_endpoint_descriptor acm_fs_notify_desc = { > + .bLength = USB_DT_ENDPOINT_SIZE, > + .bDescriptorType = USB_DT_ENDPOINT, > + .bEndpointAddress = 3 | USB_DIR_IN, > + .bmAttributes = USB_ENDPOINT_XFER_INT, > + .wMaxPacketSize = __constant_cpu_to_le16(64), > + .bInterval = 32, > +}; > + > +static struct usb_endpoint_descriptor acm_fs_in_desc = { > + .bLength = USB_DT_ENDPOINT_SIZE, > + .bDescriptorType = USB_DT_ENDPOINT, > + .bEndpointAddress = USB_DIR_IN, > + .bmAttributes = USB_ENDPOINT_XFER_BULK, > +}; > + > +static struct usb_endpoint_descriptor acm_fs_out_desc = { > + .bLength = USB_DT_ENDPOINT_SIZE, > + .bDescriptorType = USB_DT_ENDPOINT, > + .bEndpointAddress = USB_DIR_OUT, > + .bmAttributes = USB_ENDPOINT_XFER_BULK, > +}; > + > +static struct usb_descriptor_header *acm_fs_function[] = { > + (struct usb_descriptor_header *)&acm_iad_descriptor, > + (struct usb_descriptor_header *)&acm_control_intf_desc, > + (struct usb_descriptor_header *)&acm_header_desc, > + (struct usb_descriptor_header *)&acm_call_mgmt_desc, > + (struct usb_descriptor_header *)&acm_desc, > + (struct usb_descriptor_header *)&acm_union_desc, > + (struct usb_descriptor_header *)&acm_fs_notify_desc, > + (struct usb_descriptor_header *)&acm_data_intf_desc, > + (struct usb_descriptor_header *)&acm_fs_in_desc, > + (struct usb_descriptor_header *)&acm_fs_out_desc, > + NULL, > +}; > + > +static struct usb_endpoint_descriptor acm_hs_notify_desc = { > + .bLength = USB_DT_ENDPOINT_SIZE, > + .bDescriptorType = USB_DT_ENDPOINT, > + .bmAttributes = USB_ENDPOINT_XFER_INT, > + .wMaxPacketSize = __constant_cpu_to_le16(64), > + .bInterval = 11, > +}; > + > +static struct usb_endpoint_descriptor acm_hs_in_desc = { > + .bLength = USB_DT_ENDPOINT_SIZE, > + .bDescriptorType = USB_DT_ENDPOINT, > + .bmAttributes = USB_ENDPOINT_XFER_BULK, > + .wMaxPacketSize = __constant_cpu_to_le16(512), > +}; > + > +static struct usb_endpoint_descriptor acm_hs_out_desc = { > + .bLength = USB_DT_ENDPOINT_SIZE, > + .bDescriptorType = USB_DT_ENDPOINT, > + .bmAttributes = USB_ENDPOINT_XFER_BULK, > + .wMaxPacketSize = __constant_cpu_to_le16(512), > +}; > + > +static struct usb_descriptor_header *acm_hs_function[] = { > + (struct usb_descriptor_header *)&acm_iad_descriptor, > + (struct usb_descriptor_header *)&acm_control_intf_desc, > + (struct usb_descriptor_header *)&acm_header_desc, > + (struct usb_descriptor_header *)&acm_call_mgmt_desc, > + (struct usb_descriptor_header *)&acm_desc, > + (struct usb_descriptor_header *)&acm_union_desc, > + (struct usb_descriptor_header *)&acm_hs_notify_desc, > + (struct usb_descriptor_header *)&acm_data_intf_desc, > + (struct usb_descriptor_header *)&acm_hs_in_desc, > + (struct usb_descriptor_header *)&acm_hs_out_desc, > + NULL, > +}; > + > +static inline struct usb_endpoint_descriptor * > +ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *hs, > + struct usb_endpoint_descriptor *fs) > +{ > + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) > + return hs; > + return fs; > +} > + > +static int acm_bind(struct usb_configuration *c, struct usb_function *f) > +{ > + struct usb_gadget *gadget = c->cdev->gadget; > + struct f_acm *f_acm = func_to_acm(f); > + struct usb_ep *ep; > + int id; > + > + id = usb_interface_id(c, f); > + if (id < 0) > + return id; > + > + acm_iad_descriptor.bFirstInterface = id; > + acm_control_intf_desc.bInterfaceNumber = id; > + acm_union_desc.bMasterInterface0 = id; > + > + f_acm->ctrl_id = id; > + > + id = usb_interface_id(c, f); > + if (id < 0) > + return id; > + > + acm_data_intf_desc.bInterfaceNumber = id; > + acm_union_desc.bSlaveInterface0 = id; > + acm_call_mgmt_desc.bDataInterface = id; > + > + f_acm->data_id = id; > + > + /* allocate instance-specific endpoints */ > + ep = usb_ep_autoconfig(gadget, &acm_fs_in_desc); > + if (!ep) > + return -ENODEV; > + > + f_acm->ep_in = ep; > + > + ep = usb_ep_autoconfig(gadget, &acm_fs_out_desc); > + if (!ep) > + return -ENODEV; > + > + f_acm->ep_out = ep; > + > + ep = usb_ep_autoconfig(gadget, &acm_fs_notify_desc); > + if (!ep) > + return -ENODEV; > + > + f_acm->ep_notify = ep; > + > + if (gadget_is_dualspeed(gadget)) { > + /* Assume endpoint addresses are the same for both speeds */ > + acm_hs_in_desc.bEndpointAddress = > acm_fs_in_desc.bEndpointAddress; > + acm_hs_out_desc.bEndpointAddress = > acm_fs_out_desc.bEndpointAddress; > + acm_hs_notify_desc.bEndpointAddress = > acm_fs_notify_desc.bEndpointAddress; > + } > + > + return 0; > +} > + > +static void acm_unbind(struct usb_configuration *c, struct usb_function *f) > +{ > + struct f_acm *f_acm = func_to_acm(f); > + > + if (default_acm_function == f_acm) > + default_acm_function = NULL; > + > + buf_free(&f_acm->rx_buf); > + buf_free(&f_acm->tx_buf); > + > + free(f_acm); > +} > + > +static void acm_notify_complete(struct usb_ep *ep, struct usb_request *req) > +{ > + /* nothing to do */ > +} > + > +static void acm_tx_complete(struct usb_ep *ep, struct usb_request *req) > +{ > + struct f_acm *f_acm = req->context; > + > + f_acm->tx_on = true; > +} > + > +static void acm_rx_complete(struct usb_ep *ep, struct usb_request *req) > +{ > + struct f_acm *f_acm = req->context; > + > + buf_push(&f_acm->rx_buf, req->buf, req->actual); > + > + /* Queue RX req again */ > + req->actual = 0; > + usb_ep_queue(ep, req, 0); > +} > + > +static struct usb_request *acm_start_ep(struct usb_ep *ep, void *complete_cb, > + void *context) > +{ > + struct usb_request *req; > + > + req = usb_ep_alloc_request(ep, 0); > + if (!req) > + return NULL; > + > + req->length = REQ_SIZE_MAX; > + req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, REQ_SIZE_MAX); > + if (!req->buf) { > + usb_ep_free_request(ep, req); > + return NULL; > + } > + > + memset(req->buf, 0, req->length); > + req->complete = complete_cb; > + req->context = context; > + > + return req; > +} > + > +static int acm_start_data(struct f_acm *f_acm, struct usb_gadget *gadget) > +{ > + const struct usb_endpoint_descriptor *d; > + int ret; > + > + /* EP IN */ > + d = ep_desc(gadget, &acm_hs_in_desc, &acm_fs_in_desc); > + ret = usb_ep_enable(f_acm->ep_in, d); > + if (ret) > + return ret; > + > + f_acm->req_in = acm_start_ep(f_acm->ep_in, acm_tx_complete, f_acm); > + > + /* EP OUT */ > + d = ep_desc(gadget, &acm_hs_out_desc, &acm_fs_out_desc); > + ret = usb_ep_enable(f_acm->ep_out, d); > + if (ret) > + return ret; > + > + f_acm->req_out = acm_start_ep(f_acm->ep_out, acm_rx_complete, f_acm); > + > + /* Start OUT transfer (EP OUT) */ > + ret = usb_ep_queue(f_acm->ep_out, f_acm->req_out, 0); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int acm_start_ctrl(struct f_acm *f_acm, struct usb_gadget *gadget) > +{ > + const struct usb_endpoint_descriptor *d; > + > + usb_ep_disable(f_acm->ep_notify); > + > + d = ep_desc(gadget, &acm_hs_notify_desc, &acm_fs_notify_desc); > + usb_ep_enable(f_acm->ep_notify, d); > + > + acm_start_ep(f_acm->ep_notify, acm_notify_complete, f_acm); > + > + return 0; > +} > + > +static int acm_set_alt(struct usb_function *f, unsigned int intf, unsigned > int alt) > +{ > + struct usb_gadget *gadget = f->config->cdev->gadget; > + struct f_acm *f_acm = func_to_acm(f); > + > + if (intf == f_acm->ctrl_id) { > + return acm_start_ctrl(f_acm, gadget); > + } else if (intf == f_acm->data_id) { > + acm_start_data(f_acm, gadget); > + f_acm->connected = true; > + f_acm->tx_on = true; > + return 0; > + } > + > + return -EINVAL; > +} > + > +static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest > *ctrl) > +{ > + struct usb_gadget *gadget = f->config->cdev->gadget; > + struct usb_request *req = f->config->cdev->req; > + u16 w_index = le16_to_cpu(ctrl->wIndex); > + u16 w_value = le16_to_cpu(ctrl->wValue); > + u16 w_length = le16_to_cpu(ctrl->wLength); > + struct f_acm *f_acm = func_to_acm(f); > + int value = -1; > + > + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { > + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) > + | USB_CDC_REQ_SET_LINE_CODING: > + /* SET_LINE_CODING */ > + > + if (w_length != sizeof(f_acm->line_coding) || w_index != > f_acm->ctrl_id) > + goto invalid; > + > + value = w_length; > + > + memcpy(&f_acm->line_coding, req->buf, value); > + > + break; > + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) > + | USB_CDC_REQ_GET_LINE_CODING: > + /* GET_LINE_CODING */ > + > + if (w_length != sizeof(f_acm->line_coding) || w_index != > f_acm->ctrl_id) > + goto invalid; > + > + value = w_length; > + > + memcpy(req->buf, &f_acm->line_coding, value); > + > + break; > + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) > + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: > + /* SET_CONTROL_LINE_STATE */ > + > + if (w_index != f_acm->ctrl_id) > + goto invalid; > + > + value = 0; > + > + f_acm->handshake_bits = w_value; > + > + break; > + default: > +invalid: > + printf("invalid control req%02x.%02x v%04x i%04x l%d\n", > + ctrl->bRequestType, ctrl->bRequest, w_value, w_index, > + w_length); > + } > + > + /* respond with data transfer or status phase? */ > + if (value >= 0) { > + req->zero = 0; > + req->length = value; > + usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); > + } > + > + return 0; > +} > + > +static void acm_disable(struct usb_function *f) > +{ > + struct f_acm *f_acm = func_to_acm(f); > + > + usb_ep_disable(f_acm->ep_out); > + usb_ep_disable(f_acm->ep_in); > + usb_ep_disable(f_acm->ep_notify); > + > + if (f_acm->req_out) { > + free(f_acm->req_out->buf); > + usb_ep_free_request(f_acm->ep_out, f_acm->req_out); > + f_acm->req_out = NULL; > + } > + > + if (f_acm->req_in) { > + free(f_acm->req_in->buf); > + usb_ep_free_request(f_acm->ep_in, f_acm->req_in); > + f_acm->req_in = NULL; > + } > +} > + > +/* static strings, in UTF-8 */ > +static struct usb_string acm_string_defs[] = { > + [0].s = "CDC Abstract Control Model (ACM)", > + [1].s = "CDC ACM Data", > + [2].s = "CDC Serial", > + { } /* end of list */ > +}; > + > +static struct usb_gadget_strings acm_string_table = { > + .language = 0x0409, /* en-us */ > + .strings = acm_string_defs, > +}; > + > +static struct usb_gadget_strings *acm_strings[] = { > + &acm_string_table, > + NULL, > +}; > + > +static void __acm_tx(struct f_acm *f_acm) > +{ > + int len, ret; > + > + do { > + usb_gadget_handle_interrupts(f_acm->controller_index); > + > + if (!(f_acm->handshake_bits & ACM_CTRL_DTR)) > + break; > + > + if (!f_acm->tx_on) > + continue; > + > + len = buf_pop(&f_acm->tx_buf, f_acm->req_in->buf, REQ_SIZE_MAX); > + if (!len) > + break; > + > + f_acm->req_in->length = len; > + > + ret = usb_ep_queue(f_acm->ep_in, f_acm->req_in, 0); > + if (ret) > + break; > + > + f_acm->tx_on = false; > + > + /* Do not reset the watchdog, if TX is stuck there is probably > + * a real issue. > + */ > + } while (1); > +} > + > +static bool acm_connected(struct stdio_dev *dev) > +{ > + struct f_acm *f_acm = stdio_to_acm(dev); > + > + /* give a chance to process udc irq */ > + usb_gadget_handle_interrupts(f_acm->controller_index); > + > + return f_acm->connected; > +} > + > +static int acm_add(struct usb_configuration *c) > +{ > + struct f_acm *f_acm; > + int status; > + > + f_acm = calloc(1, sizeof(*f_acm)); > + if (!f_acm) > + return -ENOMEM; > + > + f_acm->usb_function.name = "f_acm"; > + f_acm->usb_function.bind = acm_bind; > + f_acm->usb_function.unbind = acm_unbind; > + f_acm->usb_function.set_alt = acm_set_alt; > + f_acm->usb_function.disable = acm_disable; > + f_acm->usb_function.strings = acm_strings; > + f_acm->usb_function.descriptors = acm_fs_function; > + f_acm->usb_function.hs_descriptors = acm_hs_function; > + f_acm->usb_function.setup = acm_setup; > + f_acm->controller_index = 0; > + > + status = usb_add_function(c, &f_acm->usb_function); > + if (status) { > + free(f_acm); > + return status; > + } > + > + buf_init(&f_acm->rx_buf, 2048); > + buf_init(&f_acm->tx_buf, 2048); > + > + if (!default_acm_function) > + default_acm_function = f_acm; > + > + return status; > +} > + > +DECLARE_GADGET_BIND_CALLBACK(usb_serial_acm, acm_add); > + > +/* STDIO */ > +static int acm_stdio_tstc(struct stdio_dev *dev) > +{ > + struct f_acm *f_acm = stdio_to_acm(dev); > + > + usb_gadget_handle_interrupts(f_acm->controller_index); > + > + return (f_acm->rx_buf.size > 0); > +} > + > +static int acm_stdio_getc(struct stdio_dev *dev) > +{ > + struct f_acm *f_acm = stdio_to_acm(dev); > + char c; > + > + /* Wait for a character to arrive. */ > + while (!acm_stdio_tstc(dev)) > + WATCHDOG_RESET(); > + > + buf_pop(&f_acm->rx_buf, &c, 1); > + > + return c; > +} > + > +static void acm_stdio_putc(struct stdio_dev *dev, const char c) > +{ > + struct f_acm *f_acm = stdio_to_acm(dev); > + > + if (c == '\n') > + buf_push(&f_acm->tx_buf, "\r", 1); > + > + buf_push(&f_acm->tx_buf, &c, 1); > + > + if (!f_acm->connected) > + return; > + > + __acm_tx(f_acm); > +} > + > +static void acm_stdio_puts(struct stdio_dev *dev, const char *str) > +{ > + struct f_acm *f_acm = stdio_to_acm(dev); > + > + while (*str) { > + if (*str == '\n') > + buf_push(&f_acm->tx_buf, "\r", 1); > + > + buf_push(&f_acm->tx_buf, str++, 1); > + } > + > + if (!f_acm->connected) > + return; > + > + __acm_tx(f_acm); > +} > + > +static int acm_stdio_start(struct stdio_dev *dev) > +{ > + int ret; > + > + if (dev->priv) { /* function already exist */ > + return 0; > + } > + > + ret = g_dnl_register("usb_serial_acm"); > + if (ret) > + return ret; > + > + if (default_acm_function) > + dev->priv = default_acm_function; > + else > + return -ENODEV; > + > + while (!acm_connected(dev)) { > + if (ctrlc()) > + return -ECANCELED; > + > + WATCHDOG_RESET(); > + } > + > + return 0; > +} > + > +static int acm_stdio_stop(struct stdio_dev *dev) > +{ > + g_dnl_unregister(); > + g_dnl_clear_detach(); > + > + return 0; > +} > + > +int drv_usbacm_init(void) > +{ > + struct stdio_dev stdio; > + > + strcpy(stdio.name, "usbacm"); > + stdio.flags = DEV_FLAGS_INPUT | DEV_FLAGS_OUTPUT; > + stdio.tstc = acm_stdio_tstc; > + stdio.getc = acm_stdio_getc; > + stdio.putc = acm_stdio_putc; > + stdio.puts = acm_stdio_puts; > + stdio.start = acm_stdio_start; > + stdio.stop = acm_stdio_stop; > + stdio.priv = NULL; > + stdio.ext = 0; > + > + return stdio_register(&stdio); > +} > diff --git a/include/stdio_dev.h b/include/stdio_dev.h > index 8fb9a12..270fa27 100644 > --- a/include/stdio_dev.h > +++ b/include/stdio_dev.h > @@ -103,6 +103,7 @@ int drv_lcd_init(void); > int drv_video_init(void); > int drv_keyboard_init(void); > int drv_usbtty_init(void); > +int drv_usbacm_init(void); > int drv_nc_init(void); > int drv_jtag_console_init(void); > int cbmemc_init(void); > -- > 2.7.4 >