aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNIIBE Yutaka <gniibe@fsij.org>2017-06-27 15:45:04 +0900
committerNIIBE Yutaka <gniibe@fsij.org>2017-06-27 15:46:04 +0900
commitd142cbbea3d40ef6b196c434298237c63204cd1a (patch)
tree5c38a4905a94350edc857cab4a499276529b717b
parentf011d643e99a847f3c537ccfa9967858b4f8a5fc (diff)
Added CDC-ACM sample of USBIP.
-rw-r--r--ChangeLog4
-rw-r--r--example-cdc-gnu-linux/Makefile37
-rw-r--r--example-cdc-gnu-linux/README35
l---------example-cdc-gnu-linux/board.h1
-rw-r--r--example-cdc-gnu-linux/sample.c197
-rw-r--r--example-cdc-gnu-linux/tty.h9
-rw-r--r--example-cdc-gnu-linux/usb-cdc.c953
7 files changed, 1236 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 362f660..d94361e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2017-06-27 NIIBE Yutaka <gniibe@fsij.org>
+
+ * example-cdc-gnu-linux/README: New.
+
2017-06-26 NIIBE Yutaka <gniibe@fsij.org>
* board/board-gnu-linux.h: New.
diff --git a/example-cdc-gnu-linux/Makefile b/example-cdc-gnu-linux/Makefile
new file mode 100644
index 0000000..02d68b2
--- /dev/null
+++ b/example-cdc-gnu-linux/Makefile
@@ -0,0 +1,37 @@
+# Makefile for example application of Chopstx
+
+PROJECT = sample
+
+### This is for GNU/Linux
+
+CHOPSTX = ..
+LDSCRIPT=
+CSRC = sample.c usb-cdc.c
+
+CHIP=gnu-linux
+# USE_SYS = yes
+USE_SYS =
+USE_USB = yes
+EMULATION=yes
+
+###################################
+CROSS =
+CC = $(CROSS)gcc
+LD = $(CROSS)gcc
+OBJCOPY = $(CROSS)objcopy
+
+MCU = none
+CWARN = -Wall -Wextra -Wstrict-prototypes
+DEFS = -DGNU_LINUX_EMULATION
+OPT = -g # -O3 -Os
+LIBS = -lpthread
+
+####################
+include ../rules.mk
+
+board.h:
+ @echo Please make a symbolic link \'board.h\' to a file in ../board;
+ @exit 1
+
+distclean: clean
+ rm -f board.h
diff --git a/example-cdc-gnu-linux/README b/example-cdc-gnu-linux/README
new file mode 100644
index 0000000..34d21bc
--- /dev/null
+++ b/example-cdc-gnu-linux/README
@@ -0,0 +1,35 @@
+(1) preparation as root
+
+Don't run ModemManager
+
+# systemctl stop ModemManager
+
+Install USBIP host module
+# modprobe vhci_hcd
+
+
+(2) Run sample program of USBIP server
+
+$ ./sample
+
+
+(3) Use the USB device as root
+
+Attach the Chopstx application program (USBIP device) to USBIP host.
+
+# usbip attach -r 127.0.0.1 -b 1-1
+
+
+(4) Connect CDC-ACM device
+
+$ cu -l /dev/ttyACM0
+
+Then, you see the message from Chopstx. As you type lines, it echos.
+
+Type RET, ~ then . , you can terminate the session.
+
+
+(5) Detach the USBIP device as root
+
+# usbip detach -p 0
+
diff --git a/example-cdc-gnu-linux/board.h b/example-cdc-gnu-linux/board.h
new file mode 120000
index 0000000..2ba9f86
--- /dev/null
+++ b/example-cdc-gnu-linux/board.h
@@ -0,0 +1 @@
+../board/board-gnu-linux.h \ No newline at end of file
diff --git a/example-cdc-gnu-linux/sample.c b/example-cdc-gnu-linux/sample.c
new file mode 100644
index 0000000..fd80f78
--- /dev/null
+++ b/example-cdc-gnu-linux/sample.c
@@ -0,0 +1,197 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ucontext.h>
+
+#include <chopstx.h>
+
+#include "usb_lld.h"
+#include "tty.h"
+
+#include <unistd.h>
+#include <stdio.h>
+
+static void
+set_led (int on)
+{
+#if 0
+ /* For debugging, no output of LED. */
+#if 1
+ if (on)
+ write (1, "********\x08\x08\x08\x08\x08\x08\x08\x08", 16);
+ else
+ write (1, " \x08\x08\x08\x08\x08\x08\x08\x08", 16);
+#else
+ if (on)
+ puts ("!");
+ else
+ puts ("");
+#endif
+#else
+ (void)on;
+#endif
+}
+
+static chopstx_mutex_t mtx;
+static chopstx_cond_t cnd0;
+static chopstx_cond_t cnd1;
+
+static uint8_t u, v;
+static uint8_t m; /* 0..100 */
+
+static void *
+pwm (void *arg)
+{
+ (void)arg;
+
+ chopstx_mutex_lock (&mtx);
+ chopstx_cond_wait (&cnd0, &mtx);
+ chopstx_mutex_unlock (&mtx);
+
+ while (1)
+ {
+ set_led (u&v);
+ chopstx_usec_wait (m);
+ set_led (0);
+ chopstx_usec_wait (100-m);
+ }
+
+ return NULL;
+}
+
+static void *
+blk (void *arg)
+{
+ (void)arg;
+
+ chopstx_mutex_lock (&mtx);
+ chopstx_cond_wait (&cnd1, &mtx);
+ chopstx_mutex_unlock (&mtx);
+
+ while (1)
+ {
+ v = 0;
+ chopstx_usec_wait (200*1000);
+ v = 1;
+ chopstx_usec_wait (200*1000);
+ }
+
+ return NULL;
+}
+
+
+#define PRIO_PWM 3
+#define PRIO_BLK 2
+
+static char __process1_stack_base__[4096];
+static char __process2_stack_base__[4096];
+
+#define STACK_ADDR_PWM ((uintptr_t)__process1_stack_base__)
+#define STACK_SIZE_PWM (sizeof __process1_stack_base__)
+
+#define STACK_ADDR_BLK ((uintptr_t)__process2_stack_base__)
+#define STACK_SIZE_BLK (sizeof __process2_stack_base__)
+
+
+static char hexchar (uint8_t x)
+{
+ x &= 0x0f;
+ if (x <= 0x09)
+ return '0' + x;
+ else if (x <= 0x0f)
+ return 'a' + x - 10;
+ else
+ return '?';
+}
+
+
+#ifdef GNU_LINUX_EMULATION
+#define main emulated_main
+#endif
+
+int
+main (int argc, const char *argv[])
+{
+ struct tty *tty;
+ uint8_t count;
+
+ (void)argc;
+ (void)argv;
+
+ chopstx_mutex_init (&mtx);
+ chopstx_cond_init (&cnd0);
+ chopstx_cond_init (&cnd1);
+
+ m = 10;
+
+ chopstx_create (PRIO_PWM, STACK_ADDR_PWM, STACK_SIZE_PWM, pwm, NULL);
+ chopstx_create (PRIO_BLK, STACK_ADDR_BLK, STACK_SIZE_BLK, blk, NULL);
+
+ chopstx_usec_wait (200*1000);
+
+ chopstx_mutex_lock (&mtx);
+ chopstx_cond_signal (&cnd0);
+ chopstx_cond_signal (&cnd1);
+ chopstx_mutex_unlock (&mtx);
+
+ u = 1;
+
+ tty = tty_open ();
+ tty_wait_configured (tty);
+
+ count = 0;
+ m = 50;
+ while (1)
+ {
+ char s[LINEBUFSIZE];
+
+ u = 1;
+ tty_wait_connection (tty);
+
+ chopstx_usec_wait (50*1000);
+
+ puts("send ZLP");
+ /* Send ZLP at the beginning. */
+ tty_send (tty, s, 0);
+
+ memcpy (s, "xx: Hello, World with Chopstx!\r\n", 32);
+ s[0] = hexchar (count >> 4);
+ s[1] = hexchar (count & 0x0f);
+ count++;
+
+ puts("send hello");
+ if (tty_send (tty, s, 32) < 0)
+ continue;
+
+ while (1)
+ {
+ int size;
+ uint32_t usec;
+
+ puts("recv msg");
+ usec = 3000000; /* 3.0 seconds */
+ size = tty_recv (tty, s + 4, &usec);
+ if (size < 0)
+ break;
+
+ if (size)
+ {
+ size--;
+
+ puts("send msg");
+ s[0] = hexchar (size >> 4);
+ s[1] = hexchar (size & 0x0f);
+ s[2] = ':';
+ s[3] = ' ';
+ s[size + 4] = '\r';
+ s[size + 5] = '\n';
+ if (tty_send (tty, s, size + 6) < 0)
+ break;
+ }
+
+ u ^= 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/example-cdc-gnu-linux/tty.h b/example-cdc-gnu-linux/tty.h
new file mode 100644
index 0000000..77e7f46
--- /dev/null
+++ b/example-cdc-gnu-linux/tty.h
@@ -0,0 +1,9 @@
+#define LINEBUFSIZE 128
+
+struct tty;
+
+struct tty *tty_open (void);
+void tty_wait_configured (struct tty *tty);
+void tty_wait_connection (struct tty *tty);
+int tty_send (struct tty *tty, const char *buf, int count);
+int tty_recv (struct tty *tty, char *buf, uint32_t *timeout);
diff --git a/example-cdc-gnu-linux/usb-cdc.c b/example-cdc-gnu-linux/usb-cdc.c
new file mode 100644
index 0000000..79fa2f0
--- /dev/null
+++ b/example-cdc-gnu-linux/usb-cdc.c
@@ -0,0 +1,953 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <chopstx.h>
+#include <string.h>
+#include "usb_lld.h"
+#include "tty.h"
+
+static chopstx_intr_t usb_intr;
+
+struct line_coding
+{
+ uint32_t bitrate;
+ uint8_t format;
+ uint8_t paritytype;
+ uint8_t datatype;
+} __attribute__((packed));
+
+static const struct line_coding line_coding0 = {
+ 115200, /* baud rate: 115200 */
+ 0x00, /* stop bits: 1 */
+ 0x00, /* parity: none */
+ 0x08 /* bits: 8 */
+};
+
+/*
+ * Currently, we only support a single TTY.
+ *
+ * It is possible to extend to support multiple TTYs, for multiple
+ * interfaces.
+ *
+ * In that case, add argument to TTY_OPEN function and
+ * modify TTY_GET function to get the TTY structure. Functions which
+ * directy accesses TTY0 (usb_device_reset and usb_set_configuration)
+ * should be modified, too.
+ *
+ * Modification of TTY_MAIN thread will be also needed to echo back
+ * input for each TTY, and the thread should run if one of TTY is
+ * opened.
+ */
+
+struct tty {
+ chopstx_mutex_t mtx;
+ chopstx_cond_t cnd;
+ uint8_t inputline[LINEBUFSIZE]; /* Line editing is supported */
+ uint8_t send_buf[LINEBUFSIZE]; /* Sending ring buffer for echo back */
+ uint8_t send_buf0[64];
+ uint8_t recv_buf0[64];
+ uint32_t inputline_len : 8;
+ uint32_t send_head : 8;
+ uint32_t send_tail : 8;
+ uint32_t flag_connected : 1;
+ uint32_t flag_send_ready : 1;
+ uint32_t flag_input_avail : 1;
+ uint32_t : 2;
+ uint32_t device_state : 3; /* USB device status */
+ struct line_coding line_coding;
+};
+
+static struct tty tty0;
+
+/*
+ * Locate TTY structure from interface number or endpoint number.
+ * Currently, it always returns tty0, because we only have the one.
+ */
+static struct tty *
+tty_get (int interface, uint8_t ep_num)
+{
+ struct tty *t = &tty0;
+
+ if (interface >= 0)
+ {
+ if (interface == 0)
+ t = &tty0;
+ }
+ else
+ {
+ if (ep_num == ENDP1 || ep_num == ENDP2 || ep_num == ENDP3)
+ t = &tty0;
+ }
+
+ return t;
+}
+
+
+#define USB_CDC_REQ_SET_LINE_CODING 0x20
+#define USB_CDC_REQ_GET_LINE_CODING 0x21
+#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22
+#define USB_CDC_REQ_SEND_BREAK 0x23
+
+/* USB Device Descriptor */
+static const uint8_t vcom_device_desc[18] = {
+ 18, /* bLength */
+ DEVICE_DESCRIPTOR, /* bDescriptorType */
+ 0x10, 0x01, /* bcdUSB = 1.1 */
+ 0x02, /* bDeviceClass (CDC). */
+ 0x00, /* bDeviceSubClass. */
+ 0x00, /* bDeviceProtocol. */
+ 0x40, /* bMaxPacketSize. */
+ 0xFF, 0xFF, /* idVendor */
+ 0x01, 0x00, /* idProduct */
+ 0x00, 0x01, /* bcdDevice */
+ 1, /* iManufacturer. */
+ 2, /* iProduct. */
+ 3, /* iSerialNumber. */
+ 1 /* bNumConfigurations. */
+};
+
+#define VCOM_FEATURE_BUS_POWERED 0x80
+
+/* Configuration Descriptor tree for a CDC.*/
+static const uint8_t vcom_config_desc[67] = {
+ 9,
+ CONFIG_DESCRIPTOR, /* bDescriptorType: Configuration */
+ /* Configuration Descriptor.*/
+ 67, 0x00, /* wTotalLength. */
+ 0x02, /* bNumInterfaces. */
+ 0x01, /* bConfigurationValue. */
+ 0, /* iConfiguration. */
+ VCOM_FEATURE_BUS_POWERED, /* bmAttributes. */
+ 50, /* bMaxPower (100mA). */
+ /* Interface Descriptor.*/
+ 9,
+ INTERFACE_DESCRIPTOR,
+ 0x00, /* bInterfaceNumber. */
+ 0x00, /* bAlternateSetting. */
+ 0x01, /* bNumEndpoints. */
+ 0x02, /* bInterfaceClass (Communications Interface Class,
+ CDC section 4.2). */
+ 0x02, /* bInterfaceSubClass (Abstract Control Model, CDC
+ section 4.3). */
+ 0x01, /* bInterfaceProtocol (AT commands, CDC section
+ 4.4). */
+ 0, /* iInterface. */
+ /* Header Functional Descriptor (CDC section 5.2.3).*/
+ 5, /* bLength. */
+ 0x24, /* bDescriptorType (CS_INTERFACE). */
+ 0x00, /* bDescriptorSubtype (Header Functional Descriptor). */
+ 0x10, 0x01, /* bcdCDC. */
+ /* Call Management Functional Descriptor. */
+ 5, /* bFunctionLength. */
+ 0x24, /* bDescriptorType (CS_INTERFACE). */
+ 0x01, /* bDescriptorSubtype (Call Management Functional
+ Descriptor). */
+ 0x03, /* bmCapabilities (D0+D1). */
+ 0x01, /* bDataInterface. */
+ /* ACM Functional Descriptor.*/
+ 4, /* bFunctionLength. */
+ 0x24, /* bDescriptorType (CS_INTERFACE). */
+ 0x02, /* bDescriptorSubtype (Abstract Control Management
+ Descriptor). */
+ 0x02, /* bmCapabilities. */
+ /* Union Functional Descriptor.*/
+ 5, /* bFunctionLength. */
+ 0x24, /* bDescriptorType (CS_INTERFACE). */
+ 0x06, /* bDescriptorSubtype (Union Functional
+ Descriptor). */
+ 0x00, /* bMasterInterface (Communication Class
+ Interface). */
+ 0x01, /* bSlaveInterface0 (Data Class Interface). */
+ /* Endpoint 2 Descriptor.*/
+ 7,
+ ENDPOINT_DESCRIPTOR,
+ ENDP2|0x80, /* bEndpointAddress. */
+ 0x03, /* bmAttributes (Interrupt). */
+ 0x08, 0x00, /* wMaxPacketSize. */
+ 0xFF, /* bInterval. */
+ /* Interface Descriptor.*/
+ 9,
+ INTERFACE_DESCRIPTOR, /* bDescriptorType: */
+ 0x01, /* bInterfaceNumber. */
+ 0x00, /* bAlternateSetting. */
+ 0x02, /* bNumEndpoints. */
+ 0x0A, /* bInterfaceClass (Data Class Interface, CDC section 4.5). */
+ 0x00, /* bInterfaceSubClass (CDC section 4.6). */
+ 0x00, /* bInterfaceProtocol (CDC section 4.7). */
+ 0x00, /* iInterface. */
+ /* Endpoint 3 Descriptor.*/
+ 7,
+ ENDPOINT_DESCRIPTOR, /* bDescriptorType: Endpoint */
+ ENDP3, /* bEndpointAddress. */
+ 0x02, /* bmAttributes (Bulk). */
+ 0x40, 0x00, /* wMaxPacketSize. */
+ 0x00, /* bInterval. */
+ /* Endpoint 1 Descriptor.*/
+ 7,
+ ENDPOINT_DESCRIPTOR, /* bDescriptorType: Endpoint */
+ ENDP1|0x80, /* bEndpointAddress. */
+ 0x02, /* bmAttributes (Bulk). */
+ 0x40, 0x00, /* wMaxPacketSize. */
+ 0x00 /* bInterval. */
+};
+
+
+/*
+ * U.S. English language identifier.
+ */
+static const uint8_t vcom_string0[4] = {
+ 4, /* bLength */
+ STRING_DESCRIPTOR,
+ 0x09, 0x04 /* LangID = 0x0409: US-English */
+};
+
+static const uint8_t vcom_string1[] = {
+ 23*2+2, /* bLength */
+ STRING_DESCRIPTOR, /* bDescriptorType */
+ /* Manufacturer: "Flying Stone Technology" */
+ 'F', 0, 'l', 0, 'y', 0, 'i', 0, 'n', 0, 'g', 0, ' ', 0, 'S', 0,
+ 't', 0, 'o', 0, 'n', 0, 'e', 0, ' ', 0, 'T', 0, 'e', 0, 'c', 0,
+ 'h', 0, 'n', 0, 'o', 0, 'l', 0, 'o', 0, 'g', 0, 'y', 0,
+};
+
+static const uint8_t vcom_string2[] = {
+ 14*2+2, /* bLength */
+ STRING_DESCRIPTOR, /* bDescriptorType */
+ /* Product name: "Chopstx Sample" */
+ 'C', 0, 'h', 0, 'o', 0, 'p', 0, 's', 0, 't', 0, 'x', 0, ' ', 0,
+ 'S', 0, 'a', 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0,
+};
+
+/*
+ * Serial Number string.
+ */
+static const uint8_t vcom_string3[28] = {
+ 28, /* bLength */
+ STRING_DESCRIPTOR, /* bDescriptorType */
+ '0', 0, '.', 0, '0', 0, '0', 0, /* Version number */
+};
+
+
+#define NUM_INTERFACES 2
+
+
+static void
+usb_device_reset (struct usb_dev *dev)
+{
+ usb_lld_reset (dev, VCOM_FEATURE_BUS_POWERED);
+
+ /* Initialize Endpoint 0 */
+ usb_lld_setup_endp (dev, ENDP0, 1, 1);
+
+ chopstx_mutex_lock (&tty0.mtx);
+ tty0.inputline_len = 0;
+ tty0.send_head = tty0.send_tail = 0;
+ tty0.flag_connected = 0;
+ tty0.flag_send_ready = 1;
+ tty0.flag_input_avail = 0;
+ tty0.device_state = ATTACHED;
+ memcpy (&tty0.line_coding, &line_coding0, sizeof (struct line_coding));
+ chopstx_mutex_unlock (&tty0.mtx);
+}
+
+
+#define CDC_CTRL_DTR 0x0001
+
+static void
+usb_ctrl_write_finish (struct usb_dev *dev)
+{
+ struct device_req *arg = &dev->dev_req;
+ uint8_t type_rcp = arg->type & (REQUEST_TYPE|RECIPIENT);
+
+ if (type_rcp == (CLASS_REQUEST | INTERFACE_RECIPIENT) && arg->index == 0
+ && USB_SETUP_SET (arg->type)
+ && arg->request == USB_CDC_REQ_SET_CONTROL_LINE_STATE)
+ {
+ struct tty *t = tty_get (arg->index, 0);
+
+ /* Open/close the connection. */
+ chopstx_mutex_lock (&t->mtx);
+ t->flag_connected = ((arg->value & CDC_CTRL_DTR) != 0);
+ chopstx_cond_signal (&t->cnd);
+ chopstx_mutex_unlock (&t->mtx);
+ }
+
+ /*
+ * The transaction was already finished. So, it is no use to call
+ * usb_lld_ctrl_error when the condition does not match.
+ */
+}
+
+
+
+static int
+vcom_port_data_setup (struct usb_dev *dev)
+{
+ struct device_req *arg = &dev->dev_req;
+
+ if (USB_SETUP_GET (arg->type))
+ {
+ struct tty *t = tty_get (arg->index, 0);
+
+ if (arg->request == USB_CDC_REQ_GET_LINE_CODING)
+ return usb_lld_ctrl_send (dev, &t->line_coding,
+ sizeof (struct line_coding));
+ }
+ else /* USB_SETUP_SET (req) */
+ {
+ if (arg->request == USB_CDC_REQ_SET_LINE_CODING
+ && arg->len == sizeof (struct line_coding))
+ {
+ struct tty *t = tty_get (arg->index, 0);
+
+ return usb_lld_ctrl_recv (dev, &t->line_coding,
+ sizeof (struct line_coding));
+ }
+ else if (arg->request == USB_CDC_REQ_SET_CONTROL_LINE_STATE)
+ return usb_lld_ctrl_ack (dev);
+ }
+
+ return -1;
+}
+
+static int
+usb_setup (struct usb_dev *dev)
+{
+ struct device_req *arg = &dev->dev_req;
+ uint8_t type_rcp = arg->type & (REQUEST_TYPE|RECIPIENT);
+
+ if (type_rcp == (CLASS_REQUEST | INTERFACE_RECIPIENT) && arg->index == 0)
+ return vcom_port_data_setup (dev);
+
+ return -1;
+}
+
+static int
+usb_get_descriptor (struct usb_dev *dev)
+{
+ struct device_req *arg = &dev->dev_req;
+ uint8_t rcp = arg->type & RECIPIENT;
+ uint8_t desc_type = (arg->value >> 8);
+ uint8_t desc_index = (arg->value & 0xff);
+
+ if (rcp != DEVICE_RECIPIENT)
+ return -1;
+
+ if (desc_type == DEVICE_DESCRIPTOR)
+ return usb_lld_ctrl_send (dev,
+ vcom_device_desc, sizeof (vcom_device_desc));
+ else if (desc_type == CONFIG_DESCRIPTOR)
+ return usb_lld_ctrl_send (dev,
+ vcom_config_desc, sizeof (vcom_config_desc));
+ else if (desc_type == STRING_DESCRIPTOR)
+ {
+ const uint8_t *str;
+ int size;
+
+ switch (desc_index)
+ {
+ case 0:
+ str = vcom_string0;
+ size = sizeof (vcom_string0);
+ break;
+ case 1:
+ str = vcom_string1;
+ size = sizeof (vcom_string1);
+ break;
+ case 2:
+ str = vcom_string2;
+ size = sizeof (vcom_string2);
+ break;
+ case 3:
+ str = vcom_string3;
+ size = sizeof (vcom_string3);
+ break;
+ default:
+ return -1;
+ }
+
+ return usb_lld_ctrl_send (dev, str, size);
+ }
+
+ return -1;
+}
+
+static void
+vcom_setup_endpoints_for_interface (struct usb_dev *dev,
+ uint16_t interface, int stop)
+{
+ if (interface == 0)
+ {
+ if (!stop)
+ usb_lld_setup_endp (dev, ENDP2, 0, 1);
+ else
+ usb_lld_stall_tx (ENDP2);
+ }
+ else if (interface == 1)
+ {
+ if (!stop)
+ {
+ usb_lld_setup_endp (dev, ENDP1, 0, 1);
+ usb_lld_setup_endp (dev, ENDP3, 1, 0);
+ /* Start with no data receiving (ENDP3 not enabled)*/
+ }
+ else
+ {
+ usb_lld_stall_tx (ENDP1);
+ usb_lld_stall_rx (ENDP3);
+ }
+ }
+}
+
+static int
+usb_set_configuration (struct usb_dev *dev)
+{
+ int i;
+ uint8_t current_conf;
+
+ current_conf = usb_lld_current_configuration (dev);
+ if (current_conf == 0)
+ {
+ if (dev->dev_req.value != 1)
+ return -1;
+
+ usb_lld_set_configuration (dev, 1);
+ for (i = 0; i < NUM_INTERFACES; i++)
+ vcom_setup_endpoints_for_interface (dev, i, 0);
+ chopstx_mutex_lock (&tty0.mtx);
+ tty0.device_state = CONFIGURED;
+ chopstx_cond_signal (&tty0.cnd);
+ chopstx_mutex_unlock (&tty0.mtx);
+ }
+ else if (current_conf != dev->dev_req.value)
+ {
+ if (dev->dev_req.value != 0)
+ return -1;
+
+ usb_lld_set_configuration (dev, 0);
+ for (i = 0; i < NUM_INTERFACES; i++)
+ vcom_setup_endpoints_for_interface (dev, i, 1);
+ chopstx_mutex_lock (&tty0.mtx);
+ tty0.device_state = ADDRESSED;
+ chopstx_cond_signal (&tty0.cnd);
+ chopstx_mutex_unlock (&tty0.mtx);
+ }
+
+ return usb_lld_ctrl_ack (dev);
+}
+
+
+static int
+usb_set_interface (struct usb_dev *dev)
+{
+ uint16_t interface = dev->dev_req.index;
+ uint16_t alt = dev->dev_req.value;
+
+ if (interface >= NUM_INTERFACES)
+ return -1;
+
+ if (alt != 0)
+ return -1;
+ else
+ {
+ vcom_setup_endpoints_for_interface (dev, interface, 0);
+ return usb_lld_ctrl_ack (dev);
+ }
+}
+
+static int
+usb_get_interface (struct usb_dev *dev)
+{
+ const uint8_t zero = 0;
+ uint16_t interface = dev->dev_req.index;
+
+ if (interface >= NUM_INTERFACES)
+ return -1;
+
+ /* We don't have alternate interface, so, always return 0. */
+ return usb_lld_ctrl_send (dev, &zero, 1);
+}
+
+static int
+usb_get_status_interface (struct usb_dev *dev)
+{
+ const uint16_t status_info = 0;
+ uint16_t interface = dev->dev_req.index;
+
+ if (interface >= NUM_INTERFACES)
+ return -1;
+
+ return usb_lld_ctrl_send (dev, &status_info, 2);
+}
+
+
+/*
+ * Put a character into the ring buffer to be send back.
+ */
+static void
+put_char_to_ringbuffer (struct tty *t, int c)
+{
+ uint32_t next = (t->send_tail + 1) % LINEBUFSIZE;
+
+ if (t->send_head == next)
+ /* full */
+ /* All that we can do is ignore this char. */
+ return;
+
+ t->send_buf[t->send_tail] = c;
+ t->send_tail = next;
+}
+
+/*
+ * Get characters from ring buffer into S.
+ */
+static int
+get_chars_from_ringbuffer (struct tty *t, uint8_t *s, int len)
+{
+ int i = 0;
+
+ if (t->send_head == t->send_tail)
+ /* Empty */
+ return i;
+
+ do
+ {
+ s[i++] = t->send_buf[t->send_head];
+ t->send_head = (t->send_head + 1) % LINEBUFSIZE;
+ }
+ while (t->send_head != t->send_tail && i < len);
+
+ return i;
+}
+
+
+static void
+tty_echo_char (struct tty *t, int c)
+{
+ put_char_to_ringbuffer (t, c);
+}
+
+static void
+usb_tx_done (uint8_t ep_num, uint16_t len)
+{
+ struct tty *t = tty_get (-1, ep_num);
+
+ (void)len;
+ if (ep_num == ENDP1)
+ {
+ chopstx_mutex_lock (&t->mtx);
+ if (t->flag_send_ready == 0)
+ {
+ t->flag_send_ready = 1;
+ chopstx_cond_signal (&t->cnd);
+ }
+ chopstx_mutex_unlock (&t->mtx);
+ }
+ else if (ep_num == ENDP2)
+ {
+ /* Nothing */
+ }
+}
+
+
+static int
+tty_input_char (struct tty *t, int c)
+{
+ unsigned int i;
+ int r = 0;
+
+ /* Process DEL, C-U, C-R, and RET as editing command. */
+ chopstx_mutex_lock (&t->mtx);
+ switch (c)
+ {
+ case 0x0d: /* Control-M */
+ t->inputline[t->inputline_len++] = '\n';
+ tty_echo_char (t, 0x0d);
+ tty_echo_char (t, 0x0a);
+ t->flag_input_avail = 1;
+ r = 1;
+ chopstx_cond_signal (&t->cnd);
+ break;
+ case 0x12: /* Control-R */
+ tty_echo_char (t, '^');
+ tty_echo_char (t, 'R');
+ tty_echo_char (t, 0x0d);
+ tty_echo_char (t, 0x0a);
+ for (i = 0; i < t->inputline_len; i++)
+ tty_echo_char (t, t->inputline[i]);
+ break;
+ case 0x15: /* Control-U */
+ for (i = 0; i < t->inputline_len; i++)
+ {
+ tty_echo_char (t, 0x08);
+ tty_echo_char (t, 0x20);
+ tty_echo_char (t, 0x08);
+ }
+ t->inputline_len = 0;
+ break;
+ case 0x7f: /* DEL */
+ if (t->inputline_len > 0)
+ {
+ tty_echo_char (t, 0x08);
+ tty_echo_char (t, 0x20);
+ tty_echo_char (t, 0x08);
+ t->inputline_len--;
+ }
+ break;
+ default:
+ if (t->inputline_len < sizeof (t->inputline) - 1)
+ {
+ tty_echo_char (t, c);
+ t->inputline[t->inputline_len++] = c;
+ }
+ else
+ /* Beep */
+ tty_echo_char (t, 0x0a);
+ break;
+ }
+ chopstx_mutex_unlock (&t->mtx);
+ return r;
+}
+
+static void
+usb_rx_ready (uint8_t ep_num, uint16_t len)
+{
+ struct tty *t = tty_get (-1, ep_num);
+ if (ep_num == ENDP3)
+ {
+ int i;
+
+ for (i = 0; i < len; i++)
+ if (tty_input_char (t, t->recv_buf0[i]))
+ break;
+
+ chopstx_mutex_lock (&t->mtx);
+ if (t->flag_input_avail == 0)
+ usb_lld_rx_enable_buf (ENDP3, t->recv_buf0, 64);
+ chopstx_mutex_unlock (&t->mtx);
+ }
+}
+
+static void *tty_main (void *arg);
+
+#include <signal.h>
+#define INTR_REQ_USB SIGUSR1
+#define PRIO_TTY 4
+
+static char __process3_stack_base__[4096];
+#define STACK_ADDR_TTY ((uintptr_t)__process3_stack_base__)
+#define STACK_SIZE_TTY (sizeof __process3_stack_base__)
+
+struct tty *
+tty_open (void)
+{
+ chopstx_mutex_init (&tty0.mtx);
+ chopstx_cond_init (&tty0.cnd);
+ tty0.inputline_len = 0;
+ tty0.send_head = tty0.send_tail = 0;
+ tty0.flag_connected = 0;
+ tty0.flag_send_ready = 1;
+ tty0.flag_input_avail = 0;
+ tty0.device_state = UNCONNECTED;
+ memcpy (&tty0.line_coding, &line_coding0, sizeof (struct line_coding));
+
+ chopstx_create (PRIO_TTY, STACK_ADDR_TTY, STACK_SIZE_TTY, tty_main, &tty0);
+ return &tty0;
+}
+
+
+static void *
+tty_main (void *arg)
+{
+ struct tty *t = arg;
+ struct usb_dev dev;
+ int e;
+
+#if defined(OLDER_SYS_H)
+ /*
+ * Historically (before sys < 3.0), NVIC priority setting for USB
+ * interrupt was done in usb_lld_sys_init. Thus this code.
+ *
+ * When USB interrupt occurs between usb_lld_init (which assumes
+ * ISR) and chopstx_claim_irq (which clears pending interrupt),
+ * invocation of usb_lld_event_handler won't occur.
+ *
+ * Calling usb_lld_event_handler is no harm even if there were no
+ * interrupts, thus, we call it unconditionally here, just in case
+ * if there is a request.
+ *
+ * We can't call usb_lld_init after chopstx_claim_irq, as
+ * usb_lld_init does its own setting for NVIC. Calling
+ * chopstx_claim_irq after usb_lld_init overrides that.
+ *
+ */
+ usb_lld_init (&dev, VCOM_FEATURE_BUS_POWERED);
+ chopstx_claim_irq (&usb_intr, INTR_REQ_USB);
+ goto event_handle;
+#else
+ chopstx_claim_irq (&usb_intr, INTR_REQ_USB);
+ usb_lld_init (&dev, VCOM_FEATURE_BUS_POWERED);
+#endif
+
+ while (1)
+ {
+ chopstx_intr_wait (&usb_intr);
+ if (usb_intr.ready)
+ {
+ uint8_t ep_num;
+#if defined(OLDER_SYS_H)
+ event_handle:
+#endif
+ /*
+ * When interrupt is detected, call usb_lld_event_handler.
+ * The event may be one of following:
+ * (1) Transfer to endpoint (bulk or interrupt)
+ * In this case EP_NUM is encoded in the variable E.
+ * (2) "NONE" event: some trasfer was done, but all was
+ * done by lower layer, no other work is needed in
+ * upper layer.
+ * (3) Device events: Reset or Suspend
+ * (4) Device requests to the endpoint zero.
+ *
+ */
+ e = usb_lld_event_handler (&dev);
+ ep_num = USB_EVENT_ENDP (e);
+
+ if (ep_num != 0)
+ {
+ if (USB_EVENT_TXRX (e))
+ usb_tx_done (ep_num, USB_EVENT_LEN (e));
+ else
+ usb_rx_ready (ep_num, USB_EVENT_LEN (e));
+ }
+ else
+ switch (USB_EVENT_ID (e))
+ {
+ case USB_EVENT_DEVICE_RESET:
+ usb_device_reset (&dev);
+ continue;
+
+ case USB_EVENT_DEVICE_ADDRESSED:
+ /* The addres is assigned to the device. We don't
+ * need to do anything for this actually, but in this
+ * application, we maintain the USB status of the
+ * device. Usually, just "continue" as EVENT_OK is
+ * OK.
+ */
+ chopstx_mutex_lock (&tty0.mtx);
+ tty0.device_state = ADDRESSED;
+ chopstx_cond_signal (&tty0.cnd);
+ chopstx_mutex_unlock (&tty0.mtx);
+ continue;
+
+ case USB_EVENT_GET_DESCRIPTOR:
+ if (usb_get_descriptor (&dev) < 0)
+ usb_lld_ctrl_error (&dev);
+ continue;
+
+ case USB_EVENT_SET_CONFIGURATION:
+ if (usb_set_configuration (&dev) < 0)
+ usb_lld_ctrl_error (&dev);
+ continue;
+
+ case USB_EVENT_SET_INTERFACE:
+ if (usb_set_interface (&dev) < 0)
+ usb_lld_ctrl_error (&dev);
+ continue;
+
+ case USB_EVENT_CTRL_REQUEST:
+ /* Device specific device request. */
+ if (usb_setup (&dev) < 0)
+ usb_lld_ctrl_error (&dev);
+ continue;
+
+ case USB_EVENT_GET_STATUS_INTERFACE:
+ if (usb_get_status_interface (&dev) < 0)
+ usb_lld_ctrl_error (&dev);
+ continue;
+
+ case USB_EVENT_GET_INTERFACE:
+ if (usb_get_interface (&dev) < 0)
+ usb_lld_ctrl_error (&dev);
+ continue;
+
+ case USB_EVENT_SET_FEATURE_DEVICE:
+ case USB_EVENT_SET_FEATURE_ENDPOINT:
+ case USB_EVENT_CLEAR_FEATURE_DEVICE:
+ case USB_EVENT_CLEAR_FEATURE_ENDPOINT:
+ usb_lld_ctrl_ack (&dev);
+ continue;
+
+ case USB_EVENT_CTRL_WRITE_FINISH:
+ /* Control WRITE transfer finished. */
+ usb_ctrl_write_finish (&dev);
+ continue;
+
+ case USB_EVENT_OK:
+ case USB_EVENT_DEVICE_SUSPEND:
+ default:
+ continue;
+ }
+ }
+
+ chopstx_mutex_lock (&t->mtx);
+ if (t->device_state == CONFIGURED && t->flag_connected
+ && t->flag_send_ready)
+ {
+ uint8_t line[32];
+ int len = get_chars_from_ringbuffer (t, line, sizeof (len));
+
+ if (len)
+ {
+ memcpy (t->send_buf0, line, len);
+ usb_lld_tx_enable_buf (ENDP1, t->send_buf0, len);
+ t->flag_send_ready = 0;
+ }
+ }
+ chopstx_mutex_unlock (&t->mtx);
+ }
+
+ return NULL;
+}
+
+
+void
+tty_wait_configured (struct tty *t)
+{
+ chopstx_mutex_lock (&t->mtx);
+ while (t->device_state != CONFIGURED)
+ chopstx_cond_wait (&t->cnd, &t->mtx);
+ chopstx_mutex_unlock (&t->mtx);
+}
+
+
+void
+tty_wait_connection (struct tty *t)
+{
+ chopstx_mutex_lock (&t->mtx);
+ while (t->flag_connected == 0)
+ chopstx_cond_wait (&t->cnd, &t->mtx);
+ t->flag_send_ready = 1;
+ t->flag_input_avail = 0;
+ t->send_head = t->send_tail = 0;
+ t->inputline_len = 0;
+ usb_lld_rx_enable_buf (ENDP3, t->recv_buf0, 64); /* Accept input for line */
+ chopstx_mutex_unlock (&t->mtx);
+}
+
+static int
+check_tx (struct tty *t)
+{
+ if (t->flag_send_ready)
+ /* TX done */
+ return 1;
+ if (t->flag_connected == 0)
+ /* Disconnected */
+ return -1;
+ return 0;
+}
+
+int
+tty_send (struct tty *t, const char *buf, int len)
+{
+ int r;
+ const char *p;
+ int count;
+
+ p = buf;
+ count = len >= 64 ? 64 : len;
+
+ while (1)
+ {
+ chopstx_mutex_lock (&t->mtx);
+ while ((r = check_tx (t)) == 0)
+ chopstx_cond_wait (&t->cnd, &t->mtx);
+ if (r > 0)
+ {
+ usb_lld_tx_enable_buf (ENDP1, p, count);
+ t->flag_send_ready = 0;
+ }
+ chopstx_mutex_unlock (&t->mtx);
+
+ len -= count;
+ p += count;
+ if (len == 0 && count != 64)
+ /*
+ * The size of the last packet should be != 0
+ * If 64, send ZLP (zelo length packet)
+ */
+ break;
+ count = len >= 64 ? 64 : len;
+ }
+
+ /* Wait until all sent. */
+ chopstx_mutex_lock (&t->mtx);
+ while ((r = check_tx (t)) == 0)
+ chopstx_cond_wait (&t->cnd, &t->mtx);
+ chopstx_mutex_unlock (&t->mtx);
+ return r;
+}
+
+
+static int
+check_rx (void *arg)
+{
+ struct tty *t = arg;
+
+ if (t->flag_input_avail)
+ /* RX */
+ return 1;
+ if (t->flag_connected == 0)
+ /* Disconnected */
+ return 1;
+ return 0;
+}
+
+/*
+ * Returns -1 on connection close
+ * 0 on timeout.
+ * >0 length of the inputline (including final \n)
+ *
+ */
+int
+tty_recv (struct tty *t, char *buf, uint32_t *timeout)
+{
+ int r;
+ chopstx_poll_cond_t poll_desc;
+
+ poll_desc.type = CHOPSTX_POLL_COND;
+ poll_desc.ready = 0;
+ poll_desc.cond = &t->cnd;
+ poll_desc.mutex = &t->mtx;
+ poll_desc.check = check_rx;
+ poll_desc.arg = t;
+
+ while (1)
+ {
+ struct chx_poll_head *pd_array[1] = {
+ (struct chx_poll_head *)&poll_desc
+ };
+ chopstx_poll (timeout, 1, pd_array);
+ chopstx_mutex_lock (&t->mtx);
+ r = check_rx (t);
+ chopstx_mutex_unlock (&t->mtx);
+ if (r || (timeout != NULL && *timeout == 0))
+ break;
+ }
+
+ chopstx_mutex_lock (&t->mtx);
+ if (t->flag_connected == 0)
+ r = -1;
+ else if (t->flag_input_avail)
+ {
+ r = t->inputline_len;
+ memcpy (buf, t->inputline, r);
+ t->flag_input_avail = 0;
+ usb_lld_rx_enable_buf (ENDP3, t->recv_buf0, 64);
+ t->inputline_len = 0;
+ }
+ else
+ r = 0;
+ chopstx_mutex_unlock (&t->mtx);
+
+ return r;
+}