blob: ba216128ab27b0242215958e1f860f0c810d0116 [file] [log] [blame]
Loic Poulainfc2b3992021-11-25 18:16:15 +01001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * USB CDC serial (ACM) function driver
4 *
5 * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
6 * Copyright (C) 2008 by David Brownell
7 * Copyright (C) 2008 by Nokia Corporation
8 * Copyright (C) 2009 by Samsung Electronics
9 * Copyright (c) 2021, Linaro Ltd <loic.poulain@linaro.org>
10 */
11
12#include <circbuf.h>
13#include <common.h>
14#include <console.h>
15#include <errno.h>
16#include <g_dnl.h>
17#include <malloc.h>
18#include <memalign.h>
19#include <stdio_dev.h>
20#include <version.h>
21#include <watchdog.h>
22
23#include <linux/usb/ch9.h>
24#include <linux/usb/gadget.h>
25#include <linux/usb/composite.h>
26#include <linux/usb/cdc.h>
27
28#define REQ_SIZE_MAX 512
29
30struct f_acm {
31 int ctrl_id;
32 int data_id;
33
34 struct usb_ep *ep_in;
35 struct usb_ep *ep_out;
36 struct usb_ep *ep_notify;
37
38 struct usb_request *req_in;
39 struct usb_request *req_out;
40
41 bool connected;
42 bool tx_on;
43
44 circbuf_t rx_buf;
45 circbuf_t tx_buf;
46
47 struct usb_function usb_function;
48
49 struct usb_cdc_line_coding line_coding;
50 u16 handshake_bits;
51#define ACM_CTRL_RTS BIT(1) /* unused with full duplex */
52#define ACM_CTRL_DTR BIT(0) /* host is ready for data r/w */
53
Marek Vasut99e05322023-09-01 11:50:00 +020054 struct udevice *udc;
Loic Poulainfc2b3992021-11-25 18:16:15 +010055};
56
57static struct f_acm *default_acm_function;
58
59static inline struct f_acm *func_to_acm(struct usb_function *f)
60{
61 return container_of(f, struct f_acm, usb_function);
62}
63
64static inline struct f_acm *stdio_to_acm(struct stdio_dev *s)
65{
66 /* stdio dev is cloned on registration, do not use container_of */
67 return s->priv;
68}
69
70static struct usb_interface_assoc_descriptor
71acm_iad_descriptor = {
72 .bLength = sizeof(acm_iad_descriptor),
73 .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
74 .bFirstInterface = 0,
75 .bInterfaceCount = 2, // control + data
76 .bFunctionClass = USB_CLASS_COMM,
77 .bFunctionSubClass = USB_CDC_SUBCLASS_ACM,
78 .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER,
79};
80
81static struct usb_interface_descriptor acm_control_intf_desc = {
82 .bLength = USB_DT_INTERFACE_SIZE,
83 .bDescriptorType = USB_DT_INTERFACE,
84 .bNumEndpoints = 1,
85 .bInterfaceClass = USB_CLASS_COMM,
86 .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
87 .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER,
88};
89
90static struct usb_interface_descriptor acm_data_intf_desc = {
91 .bLength = sizeof(acm_data_intf_desc),
92 .bDescriptorType = USB_DT_INTERFACE,
93 .bNumEndpoints = 2,
94 .bInterfaceClass = USB_CLASS_CDC_DATA,
95};
96
97static struct usb_cdc_header_desc acm_header_desc = {
98 .bLength = sizeof(acm_header_desc),
99 .bDescriptorType = USB_DT_CS_INTERFACE,
100 .bDescriptorSubType = USB_CDC_HEADER_TYPE,
101 .bcdCDC = __constant_cpu_to_le16(0x0110),
102};
103
104static struct usb_cdc_call_mgmt_descriptor acm_call_mgmt_desc = {
105 .bLength = sizeof(acm_call_mgmt_desc),
106 .bDescriptorType = USB_DT_CS_INTERFACE,
107 .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE,
108 .bmCapabilities = 0,
109 .bDataInterface = 0x01,
110};
111
112static struct usb_cdc_acm_descriptor acm_desc = {
113 .bLength = sizeof(acm_desc),
114 .bDescriptorType = USB_DT_CS_INTERFACE,
115 .bDescriptorSubType = USB_CDC_ACM_TYPE,
116 .bmCapabilities = USB_CDC_CAP_LINE,
117};
118
119static struct usb_cdc_union_desc acm_union_desc = {
120 .bLength = sizeof(acm_union_desc),
121 .bDescriptorType = USB_DT_CS_INTERFACE,
122 .bDescriptorSubType = USB_CDC_UNION_TYPE,
123 .bMasterInterface0 = 0x00,
124 .bSlaveInterface0 = 0x01,
125};
126
127static struct usb_endpoint_descriptor acm_fs_notify_desc = {
128 .bLength = USB_DT_ENDPOINT_SIZE,
129 .bDescriptorType = USB_DT_ENDPOINT,
130 .bEndpointAddress = 3 | USB_DIR_IN,
131 .bmAttributes = USB_ENDPOINT_XFER_INT,
132 .wMaxPacketSize = __constant_cpu_to_le16(64),
133 .bInterval = 32,
134};
135
136static struct usb_endpoint_descriptor acm_fs_in_desc = {
137 .bLength = USB_DT_ENDPOINT_SIZE,
138 .bDescriptorType = USB_DT_ENDPOINT,
139 .bEndpointAddress = USB_DIR_IN,
140 .bmAttributes = USB_ENDPOINT_XFER_BULK,
141};
142
143static struct usb_endpoint_descriptor acm_fs_out_desc = {
144 .bLength = USB_DT_ENDPOINT_SIZE,
145 .bDescriptorType = USB_DT_ENDPOINT,
146 .bEndpointAddress = USB_DIR_OUT,
147 .bmAttributes = USB_ENDPOINT_XFER_BULK,
148};
149
150static struct usb_descriptor_header *acm_fs_function[] = {
151 (struct usb_descriptor_header *)&acm_iad_descriptor,
152 (struct usb_descriptor_header *)&acm_control_intf_desc,
153 (struct usb_descriptor_header *)&acm_header_desc,
154 (struct usb_descriptor_header *)&acm_call_mgmt_desc,
155 (struct usb_descriptor_header *)&acm_desc,
156 (struct usb_descriptor_header *)&acm_union_desc,
157 (struct usb_descriptor_header *)&acm_fs_notify_desc,
158 (struct usb_descriptor_header *)&acm_data_intf_desc,
159 (struct usb_descriptor_header *)&acm_fs_in_desc,
160 (struct usb_descriptor_header *)&acm_fs_out_desc,
161 NULL,
162};
163
164static struct usb_endpoint_descriptor acm_hs_notify_desc = {
165 .bLength = USB_DT_ENDPOINT_SIZE,
166 .bDescriptorType = USB_DT_ENDPOINT,
167 .bmAttributes = USB_ENDPOINT_XFER_INT,
168 .wMaxPacketSize = __constant_cpu_to_le16(64),
169 .bInterval = 11,
170};
171
172static struct usb_endpoint_descriptor acm_hs_in_desc = {
173 .bLength = USB_DT_ENDPOINT_SIZE,
174 .bDescriptorType = USB_DT_ENDPOINT,
175 .bmAttributes = USB_ENDPOINT_XFER_BULK,
176 .wMaxPacketSize = __constant_cpu_to_le16(512),
177};
178
179static struct usb_endpoint_descriptor acm_hs_out_desc = {
180 .bLength = USB_DT_ENDPOINT_SIZE,
181 .bDescriptorType = USB_DT_ENDPOINT,
182 .bmAttributes = USB_ENDPOINT_XFER_BULK,
183 .wMaxPacketSize = __constant_cpu_to_le16(512),
184};
185
186static struct usb_descriptor_header *acm_hs_function[] = {
187 (struct usb_descriptor_header *)&acm_iad_descriptor,
188 (struct usb_descriptor_header *)&acm_control_intf_desc,
189 (struct usb_descriptor_header *)&acm_header_desc,
190 (struct usb_descriptor_header *)&acm_call_mgmt_desc,
191 (struct usb_descriptor_header *)&acm_desc,
192 (struct usb_descriptor_header *)&acm_union_desc,
193 (struct usb_descriptor_header *)&acm_hs_notify_desc,
194 (struct usb_descriptor_header *)&acm_data_intf_desc,
195 (struct usb_descriptor_header *)&acm_hs_in_desc,
196 (struct usb_descriptor_header *)&acm_hs_out_desc,
197 NULL,
198};
199
200static inline struct usb_endpoint_descriptor *
201ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *hs,
202 struct usb_endpoint_descriptor *fs)
203{
204 if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
205 return hs;
206 return fs;
207}
208
209static int acm_bind(struct usb_configuration *c, struct usb_function *f)
210{
211 struct usb_gadget *gadget = c->cdev->gadget;
212 struct f_acm *f_acm = func_to_acm(f);
213 struct usb_ep *ep;
214 int id;
215
216 id = usb_interface_id(c, f);
217 if (id < 0)
218 return id;
219
220 acm_iad_descriptor.bFirstInterface = id;
221 acm_control_intf_desc.bInterfaceNumber = id;
222 acm_union_desc.bMasterInterface0 = id;
223
224 f_acm->ctrl_id = id;
225
226 id = usb_interface_id(c, f);
227 if (id < 0)
228 return id;
229
230 acm_data_intf_desc.bInterfaceNumber = id;
231 acm_union_desc.bSlaveInterface0 = id;
232 acm_call_mgmt_desc.bDataInterface = id;
233
234 f_acm->data_id = id;
235
236 /* allocate instance-specific endpoints */
237 ep = usb_ep_autoconfig(gadget, &acm_fs_in_desc);
238 if (!ep)
239 return -ENODEV;
240
241 f_acm->ep_in = ep;
242
243 ep = usb_ep_autoconfig(gadget, &acm_fs_out_desc);
244 if (!ep)
245 return -ENODEV;
246
247 f_acm->ep_out = ep;
248
249 ep = usb_ep_autoconfig(gadget, &acm_fs_notify_desc);
250 if (!ep)
251 return -ENODEV;
252
253 f_acm->ep_notify = ep;
254
255 if (gadget_is_dualspeed(gadget)) {
256 /* Assume endpoint addresses are the same for both speeds */
257 acm_hs_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress;
258 acm_hs_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress;
259 acm_hs_notify_desc.bEndpointAddress = acm_fs_notify_desc.bEndpointAddress;
260 }
261
262 return 0;
263}
264
265static void acm_unbind(struct usb_configuration *c, struct usb_function *f)
266{
267 struct f_acm *f_acm = func_to_acm(f);
268
269 if (default_acm_function == f_acm)
270 default_acm_function = NULL;
271
272 buf_free(&f_acm->rx_buf);
273 buf_free(&f_acm->tx_buf);
274
275 free(f_acm);
276}
277
278static void acm_notify_complete(struct usb_ep *ep, struct usb_request *req)
279{
280 /* nothing to do */
281}
282
283static void acm_tx_complete(struct usb_ep *ep, struct usb_request *req)
284{
285 struct f_acm *f_acm = req->context;
286
287 f_acm->tx_on = true;
288}
289
290static void acm_rx_complete(struct usb_ep *ep, struct usb_request *req)
291{
292 struct f_acm *f_acm = req->context;
293
294 buf_push(&f_acm->rx_buf, req->buf, req->actual);
295
296 /* Queue RX req again */
297 req->actual = 0;
298 usb_ep_queue(ep, req, 0);
299}
300
301static struct usb_request *acm_start_ep(struct usb_ep *ep, void *complete_cb,
302 void *context)
303{
304 struct usb_request *req;
305
306 req = usb_ep_alloc_request(ep, 0);
307 if (!req)
308 return NULL;
309
310 req->length = REQ_SIZE_MAX;
311 req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, REQ_SIZE_MAX);
312 if (!req->buf) {
313 usb_ep_free_request(ep, req);
314 return NULL;
315 }
316
317 memset(req->buf, 0, req->length);
318 req->complete = complete_cb;
319 req->context = context;
320
321 return req;
322}
323
324static int acm_start_data(struct f_acm *f_acm, struct usb_gadget *gadget)
325{
326 const struct usb_endpoint_descriptor *d;
327 int ret;
328
329 /* EP IN */
330 d = ep_desc(gadget, &acm_hs_in_desc, &acm_fs_in_desc);
331 ret = usb_ep_enable(f_acm->ep_in, d);
332 if (ret)
333 return ret;
334
335 f_acm->req_in = acm_start_ep(f_acm->ep_in, acm_tx_complete, f_acm);
336
337 /* EP OUT */
338 d = ep_desc(gadget, &acm_hs_out_desc, &acm_fs_out_desc);
339 ret = usb_ep_enable(f_acm->ep_out, d);
340 if (ret)
341 return ret;
342
343 f_acm->req_out = acm_start_ep(f_acm->ep_out, acm_rx_complete, f_acm);
344
345 /* Start OUT transfer (EP OUT) */
346 ret = usb_ep_queue(f_acm->ep_out, f_acm->req_out, 0);
347 if (ret)
348 return ret;
349
350 return 0;
351}
352
353static int acm_start_ctrl(struct f_acm *f_acm, struct usb_gadget *gadget)
354{
355 const struct usb_endpoint_descriptor *d;
356
357 usb_ep_disable(f_acm->ep_notify);
358
359 d = ep_desc(gadget, &acm_hs_notify_desc, &acm_fs_notify_desc);
360 usb_ep_enable(f_acm->ep_notify, d);
361
362 acm_start_ep(f_acm->ep_notify, acm_notify_complete, f_acm);
363
364 return 0;
365}
366
367static int acm_set_alt(struct usb_function *f, unsigned int intf, unsigned int alt)
368{
369 struct usb_gadget *gadget = f->config->cdev->gadget;
370 struct f_acm *f_acm = func_to_acm(f);
371
372 if (intf == f_acm->ctrl_id) {
373 return acm_start_ctrl(f_acm, gadget);
374 } else if (intf == f_acm->data_id) {
375 acm_start_data(f_acm, gadget);
376 f_acm->connected = true;
377 f_acm->tx_on = true;
378 return 0;
379 }
380
381 return -EINVAL;
382}
383
384static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
385{
386 struct usb_gadget *gadget = f->config->cdev->gadget;
387 struct usb_request *req = f->config->cdev->req;
388 u16 w_index = le16_to_cpu(ctrl->wIndex);
389 u16 w_value = le16_to_cpu(ctrl->wValue);
390 u16 w_length = le16_to_cpu(ctrl->wLength);
391 struct f_acm *f_acm = func_to_acm(f);
392 int value = -1;
393
394 switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
395 case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
396 | USB_CDC_REQ_SET_LINE_CODING:
397 /* SET_LINE_CODING */
398
399 if (w_length != sizeof(f_acm->line_coding) || w_index != f_acm->ctrl_id)
400 goto invalid;
401
402 value = w_length;
403
404 memcpy(&f_acm->line_coding, req->buf, value);
405
406 break;
407 case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
408 | USB_CDC_REQ_GET_LINE_CODING:
409 /* GET_LINE_CODING */
410
411 if (w_length != sizeof(f_acm->line_coding) || w_index != f_acm->ctrl_id)
412 goto invalid;
413
414 value = w_length;
415
416 memcpy(req->buf, &f_acm->line_coding, value);
417
418 break;
419 case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
420 | USB_CDC_REQ_SET_CONTROL_LINE_STATE:
421 /* SET_CONTROL_LINE_STATE */
422
423 if (w_index != f_acm->ctrl_id)
424 goto invalid;
425
426 value = 0;
427
428 f_acm->handshake_bits = w_value;
429
430 break;
431 default:
432invalid:
433 printf("invalid control req%02x.%02x v%04x i%04x l%d\n",
434 ctrl->bRequestType, ctrl->bRequest, w_value, w_index,
435 w_length);
436 }
437
438 /* respond with data transfer or status phase? */
439 if (value >= 0) {
440 req->zero = 0;
441 req->length = value;
442 usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);
443 }
444
445 return 0;
446}
447
448static void acm_disable(struct usb_function *f)
449{
450 struct f_acm *f_acm = func_to_acm(f);
451
452 usb_ep_disable(f_acm->ep_out);
453 usb_ep_disable(f_acm->ep_in);
454 usb_ep_disable(f_acm->ep_notify);
455
456 if (f_acm->req_out) {
457 free(f_acm->req_out->buf);
458 usb_ep_free_request(f_acm->ep_out, f_acm->req_out);
459 f_acm->req_out = NULL;
460 }
461
462 if (f_acm->req_in) {
463 free(f_acm->req_in->buf);
464 usb_ep_free_request(f_acm->ep_in, f_acm->req_in);
465 f_acm->req_in = NULL;
466 }
467}
468
469/* static strings, in UTF-8 */
470static struct usb_string acm_string_defs[] = {
471 [0].s = "CDC Abstract Control Model (ACM)",
472 [1].s = "CDC ACM Data",
473 [2].s = "CDC Serial",
474 { } /* end of list */
475};
476
477static struct usb_gadget_strings acm_string_table = {
478 .language = 0x0409, /* en-us */
479 .strings = acm_string_defs,
480};
481
482static struct usb_gadget_strings *acm_strings[] = {
483 &acm_string_table,
484 NULL,
485};
486
487static void __acm_tx(struct f_acm *f_acm)
488{
489 int len, ret;
490
491 do {
Marek Vasut99e05322023-09-01 11:50:00 +0200492 dm_usb_gadget_handle_interrupts(f_acm->udc);
Loic Poulainfc2b3992021-11-25 18:16:15 +0100493
494 if (!(f_acm->handshake_bits & ACM_CTRL_DTR))
495 break;
496
497 if (!f_acm->tx_on)
498 continue;
499
500 len = buf_pop(&f_acm->tx_buf, f_acm->req_in->buf, REQ_SIZE_MAX);
501 if (!len)
502 break;
503
504 f_acm->req_in->length = len;
505
506 ret = usb_ep_queue(f_acm->ep_in, f_acm->req_in, 0);
507 if (ret)
508 break;
509
510 f_acm->tx_on = false;
511
512 /* Do not reset the watchdog, if TX is stuck there is probably
513 * a real issue.
514 */
515 } while (1);
516}
517
518static bool acm_connected(struct stdio_dev *dev)
519{
520 struct f_acm *f_acm = stdio_to_acm(dev);
521
522 /* give a chance to process udc irq */
Marek Vasut99e05322023-09-01 11:50:00 +0200523 dm_usb_gadget_handle_interrupts(f_acm->udc);
Loic Poulainfc2b3992021-11-25 18:16:15 +0100524
525 return f_acm->connected;
526}
527
528static int acm_add(struct usb_configuration *c)
529{
530 struct f_acm *f_acm;
531 int status;
532
533 f_acm = calloc(1, sizeof(*f_acm));
534 if (!f_acm)
535 return -ENOMEM;
536
537 f_acm->usb_function.name = "f_acm";
538 f_acm->usb_function.bind = acm_bind;
539 f_acm->usb_function.unbind = acm_unbind;
540 f_acm->usb_function.set_alt = acm_set_alt;
541 f_acm->usb_function.disable = acm_disable;
542 f_acm->usb_function.strings = acm_strings;
543 f_acm->usb_function.descriptors = acm_fs_function;
544 f_acm->usb_function.hs_descriptors = acm_hs_function;
545 f_acm->usb_function.setup = acm_setup;
Marek Vasut99e05322023-09-01 11:50:00 +0200546
547 status = udc_device_get_by_index(0, &f_acm->udc);
548 if (status)
549 return status;
Loic Poulainfc2b3992021-11-25 18:16:15 +0100550
551 status = usb_add_function(c, &f_acm->usb_function);
552 if (status) {
553 free(f_acm);
554 return status;
555 }
556
557 buf_init(&f_acm->rx_buf, 2048);
558 buf_init(&f_acm->tx_buf, 2048);
559
560 if (!default_acm_function)
561 default_acm_function = f_acm;
562
563 return status;
564}
565
566DECLARE_GADGET_BIND_CALLBACK(usb_serial_acm, acm_add);
567
568/* STDIO */
569static int acm_stdio_tstc(struct stdio_dev *dev)
570{
571 struct f_acm *f_acm = stdio_to_acm(dev);
572
Marek Vasut99e05322023-09-01 11:50:00 +0200573 dm_usb_gadget_handle_interrupts(f_acm->udc);
Loic Poulainfc2b3992021-11-25 18:16:15 +0100574
575 return (f_acm->rx_buf.size > 0);
576}
577
578static int acm_stdio_getc(struct stdio_dev *dev)
579{
580 struct f_acm *f_acm = stdio_to_acm(dev);
581 char c;
582
583 /* Wait for a character to arrive. */
584 while (!acm_stdio_tstc(dev))
Stefan Roese29caf932022-09-02 14:10:46 +0200585 schedule();
Loic Poulainfc2b3992021-11-25 18:16:15 +0100586
587 buf_pop(&f_acm->rx_buf, &c, 1);
588
589 return c;
590}
591
592static void acm_stdio_putc(struct stdio_dev *dev, const char c)
593{
594 struct f_acm *f_acm = stdio_to_acm(dev);
595
596 if (c == '\n')
597 buf_push(&f_acm->tx_buf, "\r", 1);
598
599 buf_push(&f_acm->tx_buf, &c, 1);
600
601 if (!f_acm->connected)
602 return;
603
604 __acm_tx(f_acm);
605}
606
607static void acm_stdio_puts(struct stdio_dev *dev, const char *str)
608{
609 struct f_acm *f_acm = stdio_to_acm(dev);
610
611 while (*str) {
612 if (*str == '\n')
613 buf_push(&f_acm->tx_buf, "\r", 1);
614
615 buf_push(&f_acm->tx_buf, str++, 1);
616 }
617
618 if (!f_acm->connected)
619 return;
620
621 __acm_tx(f_acm);
622}
623
624static int acm_stdio_start(struct stdio_dev *dev)
625{
Caleb Connolly17d7fb22023-10-10 16:49:42 +0100626 struct udevice *udc;
Loic Poulainfc2b3992021-11-25 18:16:15 +0100627 int ret;
628
629 if (dev->priv) { /* function already exist */
630 return 0;
631 }
632
Caleb Connolly17d7fb22023-10-10 16:49:42 +0100633 ret = udc_device_get_by_index(0, &udc);
634 if (ret) {
635 pr_err("USB init failed: %d\n", ret);
636 return ret;
637 }
638
639 g_dnl_clear_detach();
640
Loic Poulainfc2b3992021-11-25 18:16:15 +0100641 ret = g_dnl_register("usb_serial_acm");
642 if (ret)
643 return ret;
644
645 if (default_acm_function)
646 dev->priv = default_acm_function;
647 else
648 return -ENODEV;
649
650 while (!acm_connected(dev)) {
651 if (ctrlc())
652 return -ECANCELED;
653
Stefan Roese29caf932022-09-02 14:10:46 +0200654 schedule();
Loic Poulainfc2b3992021-11-25 18:16:15 +0100655 }
656
657 return 0;
658}
659
660static int acm_stdio_stop(struct stdio_dev *dev)
661{
662 g_dnl_unregister();
663 g_dnl_clear_detach();
664
665 return 0;
666}
667
668int drv_usbacm_init(void)
669{
670 struct stdio_dev stdio;
671
672 strcpy(stdio.name, "usbacm");
673 stdio.flags = DEV_FLAGS_INPUT | DEV_FLAGS_OUTPUT;
674 stdio.tstc = acm_stdio_tstc;
675 stdio.getc = acm_stdio_getc;
676 stdio.putc = acm_stdio_putc;
677 stdio.puts = acm_stdio_puts;
678 stdio.start = acm_stdio_start;
679 stdio.stop = acm_stdio_stop;
680 stdio.priv = NULL;
681 stdio.ext = 0;
682
683 return stdio_register(&stdio);
684}