aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeoff Gustafson <geoff@linux.intel.com>2017-10-13 08:07:35 -0700
committerJimmy Huang <jimmy.huang@linux.intel.com>2017-10-19 13:59:35 -0700
commite29b70002e0610946b14de2fe875d8ababc2817b (patch)
tree20473055e8af95a138124079ada7e4ea8ca2f7cb
parente6c97523c082b5d1db6a56b3fdcb3b10a81e6a34 (diff)
[webusb] Add UART support and data APIs to WebUSB
Signed-off-by: Geoff Gustafson <geoff@linux.intel.com>
-rw-r--r--docs/uart.md2
-rw-r--r--docs/webusb.md16
-rw-r--r--samples/UART.js7
-rw-r--r--samples/WebUSB.js28
-rw-r--r--src/zjs_webusb.c349
-rw-r--r--src/zjs_webusb.json1
6 files changed, 394 insertions, 9 deletions
diff --git a/docs/uart.md b/docs/uart.md
index 139152d..4a20170 100644
--- a/docs/uart.md
+++ b/docs/uart.md
@@ -68,7 +68,7 @@ UARTConnection is an [EventEmitter](./events.md) with the following events:
* `Buffer` `data`
Emitted when data is received on the UART RX line. The `data` parameter is a
-`Buffer` with the recieved data.
+`Buffer` with the received data.
### UARTConnection.write
diff --git a/docs/webusb.md b/docs/webusb.md
index 0141a39..e285e26 100644
--- a/docs/webusb.md
+++ b/docs/webusb.md
@@ -19,9 +19,15 @@ When you connect your device to a Linux PC or Mac with Chrome >= 60 running, it
will give a notification that the device would like you to visit the URL you've
set. (Windows currently prevents this from working, I believe.)
-
API Documentation
-----------------
+### Event: 'read'
+
+* `Buffer` `data`
+
+Emitted when data is received on the WebUSB RX line. The `data` parameter is a
+`Buffer` with the received data.
+
### WebUSB.setURL
`void setURL(string url);`
@@ -30,6 +36,14 @@ The `url` string should begin with "https://" in order for Chrome to accept it
and display a notification. Other URLs are valid in terms of the protocol but
will have no user-visible effect in Chrome.
+### WebUSB.write
+
+`void write(Buffer buffer);`
+
+Writes the data in `buffer` to the WebUSB TX line. By default at most 511 bytes
+can be pending at one time, so that is the maximum write size, assuming all
+previous data had already been flushed out. An error will be thrown on overflow.
+
Sample Apps
-----------
* [WebUSB sample](../samples/WebUSB.js)
diff --git a/samples/UART.js b/samples/UART.js
index b5b9ac3..e0e7ffb 100644
--- a/samples/UART.js
+++ b/samples/UART.js
@@ -1,9 +1,9 @@
// Copyright (c) 2016-2017, Intel Corporation.
// Sample to test UART module. This can be run on the Arduino 101 or QEMU. It
-// will print out 'UART write succeeded, echoing input.' to the console, then echo back any characters
-// that are typed and store the string. If enter is pressed, it will print
-// out the saved string and clear it.
+// will print out 'UART write succeeded, echoing input.' to the console, then
+// echo back any characters that are typed and store the string. If Enter is
+// pressed, it will print out the saved string and clear it.
// On the Arduino 101, this sample can be used by opening /dev/ttyACMX using
// screen or minicom:
@@ -38,4 +38,5 @@ uart.on('read', function(data) {
uart.write(new Buffer(current + '\r\n'));
}
});
+
uart.write(new Buffer('UART write succeeded, echoing input.\r\n'));
diff --git a/samples/WebUSB.js b/samples/WebUSB.js
index 924604d..f9d853d 100644
--- a/samples/WebUSB.js
+++ b/samples/WebUSB.js
@@ -1,6 +1,6 @@
// Copyright (c) 2017, Intel Corporation.
-console.log("Starting WebUSB sample...");
+console.log('Starting WebUSB sample...');
var webusb = require('webusb');
@@ -11,4 +11,28 @@ var webusb = require('webusb');
// notification for other URLs.
// suggest that the browser visit this URL to connect to the device
-webusb.setURL("https://github.com/01org/zephyr.js");
+webusb.setURL('https://01org.github.io/zephyr.js/webusb');
+
+sizes = [13, 17, 23, 19, 4, 8];
+offset = 0;
+
+// reply back to web page immediately when data received
+count = 0;
+webusb.on('read', function(bytes) {
+ count += 1
+ console.log('Received packet', count, '(' + bytes.length + ' bytes)');
+
+ var buflen = sizes[offset];
+ var buf = new Buffer(buflen);
+ try {
+ for (var j = 0; j < buflen; j++)
+ buf.writeUInt8(j, j);
+ }
+ catch (e) {
+ console.log('Error:', e.message);
+ return;
+ }
+ webusb.write(buf);
+
+ offset = (offset + 1) % sizes.length;
+});
diff --git a/src/zjs_webusb.c b/src/zjs_webusb.c
index 213aa38..23e1196 100644
--- a/src/zjs_webusb.c
+++ b/src/zjs_webusb.c
@@ -4,16 +4,40 @@
#include <string.h>
// Zephyr includes
+#include <uart.h>
#include "webusb_serial.h"
// ZJS includes
+#include "zjs_buffer.h"
+#include "zjs_event.h"
#include "zjs_util.h"
#include "zjs_webusb.h"
+// max size of pending TX and RX data
+#define RINGMAX 256
+
static jerry_value_t webusb_api = 0;
u8_t *webusb_origin_url = NULL;
+struct k_timer uart_timer;
+
+// The idea here is that there is one producer and one consumer of these
+// buffers; one side is in an interrupt handler and the other in the main
+// thread. The write index is only updated by the producer, and the read
+// index is only updated by the consumer. When the indexes are equal, the
+// buffer is empty; when the write index is one below the read index (mod
+// RINGMAX), the buffer is full with RINGMAX - 1 bytes.
+typedef struct {
+ u8_t buf[RINGMAX];
+ // TODO: maybe read and write should be accessed atomically
+ volatile int read;
+ volatile int write;
+} ringbuf_t;
+
+static ringbuf_t *tx_ring = NULL;
+static ringbuf_t *rx_ring = NULL;
+
/**
* @file
* @brief WebUSB module
@@ -217,6 +241,305 @@ static ZJS_DECL_FUNC(zjs_webusb_set_url)
return ZJS_UNDEFINED;
}
+int fill_uart(struct device *dev, ringbuf_t *ring, int length)
+{
+ // requires: dev is a UART device, ring is a ring buffer, length is the max
+ // bytes to read, length must not be enough to overflow buffer
+ // returns: total number of bytes sent
+ // effects: fill UART dev with up to length bytes from ring at index, until
+ // the fifo is full
+ int total = 0;
+ while (length > 0) {
+ int index = ring->read;
+ int sent = uart_fifo_fill(dev, &ring->buf[index], length);
+ if (sent == 0) {
+ break;
+ }
+ index += sent;
+ total += sent;
+ if (index == RINGMAX) {
+ index = 0;
+ }
+ ring->read = index;
+ if (sent == length) {
+ break;
+ }
+ length -= sent;
+ }
+ return total;
+}
+
+int read_uart(struct device *dev, ringbuf_t *ring, int length)
+{
+ // requires: dev is a UART device, ring is a ring buffer, length is the max
+ // bytes to write, length must not be enough to overflow buffer
+ // returns: true if all bytes read, false if more remain
+ // effects: consume bytes from UART dev up to length bytes into ring at
+ // index, until the fifo runs out
+ int total = 0;
+ while (length > 0) {
+ int index = ring->write;
+ int bytes = uart_fifo_read(dev, &ring->buf[index], length);
+ if (bytes == 0) {
+ break;
+ }
+ index += bytes;
+ total += bytes;
+ if (index == RINGMAX) {
+ index = 0;
+ }
+ ring->write = index;
+ if (bytes == length) {
+ break;
+ }
+ length -= bytes;
+ }
+ return total;
+}
+
+// a zjs_pre_emit callback
+static bool prepare_read(void *unused, jerry_value_t argv[], u32_t *argc,
+ const char *buffer, u32_t bytes)
+{
+ // effects: reads as many bytes as possible into a buffer argument
+ int read = rx_ring->read;
+ int write = rx_ring->write;
+ int len = 0;
+
+ if (read < write) {
+ len = write - read;
+ }
+ else {
+ len = write + RINGMAX - read;
+ }
+
+ zjs_buffer_t *zbuf;
+ ZVAL buf = zjs_buffer_create(len, &zbuf);
+ if (!zbuf) {
+ ERR_PRINT("out of memory\n");
+ return false;
+ }
+
+ len = 0;
+ if (read > write) {
+ // first read to RINGMAX
+ len = RINGMAX - read;
+ memcpy(zbuf->buffer, &rx_ring->buf[read], len);
+ read = 0;
+ }
+ if (read < write) {
+ // read the rest
+ memcpy(zbuf->buffer + len, &rx_ring->buf[read], write - read);
+ read = write;
+ }
+ rx_ring->read = read;
+
+ argv[0] = jerry_acquire_value(buf);
+ CHECK_REF_COUNT("prepare", buf);
+ *argc = 1;
+ return true;
+}
+
+static void fill_uart_from_tx(struct device *dev)
+{
+ static bool filling = false;
+ if (filling) {
+ return;
+ }
+ filling = true;
+
+ int read = tx_ring->read;
+ int write = tx_ring->write;
+ if (read == write) {
+ // empty
+ uart_irq_tx_disable(dev);
+ }
+ else {
+ int max = 0;
+ bool cont = false;
+ if (read < write) {
+ max = write - read;
+ }
+ else {
+ max = RINGMAX - read;
+ cont = true;
+ }
+ if (fill_uart(dev, tx_ring, max) == max && cont) {
+ // try wraparound section
+ max = write - tx_ring->read;
+ fill_uart(dev, tx_ring, max);
+ }
+ }
+
+ filling = false;
+}
+
+static void uart_interrupt_handler(struct device *dev)
+{
+ uart_irq_update(dev);
+
+ if (uart_irq_tx_ready(dev)) {
+ // send buffer is what JS has written that we will read and send out
+ fill_uart_from_tx(dev);
+ }
+
+ if (uart_irq_rx_ready(dev)) {
+ // receive buffer is what we will write for JS to read
+ int read = rx_ring->read;
+ int write = rx_ring->write;
+ bool wrote = false;
+ if (write + 1 == read || (write == RINGMAX -1 && read == 0)) {
+ // full
+ ERR_PRINT("receive overflow\n");
+ }
+ else {
+ int max = 0;
+ bool cont = false;
+ if (write < read) {
+ max = read - 1 - write;
+ }
+ else {
+ max = RINGMAX - write;
+ if (read == 0) {
+ --max;
+ }
+ else {
+ cont = true;
+ }
+ }
+ int bytes = read_uart(dev, rx_ring, max);
+ if (bytes > 0) {
+ wrote = true;
+ }
+ if (bytes == max && cont) {
+ // try wraparound section
+ max = read - 1 - tx_ring->write;
+ if (read_uart(dev, rx_ring, max) == max) {
+ // based on docs for uart_fifo_read, not continuing to read
+ // here could cause problems
+ ERR_PRINT("read buffer full\n");
+ }
+ }
+ }
+
+ // TODO: could make event to be cancellable to implement newline-based
+ zjs_defer_emit_event(webusb_api, "read", NULL, 0, prepare_read,
+ zjs_release_args);
+ }
+}
+
+static ZJS_DECL_FUNC(zjs_webusb_write)
+{
+ // buffer to write
+ ZJS_VALIDATE_ARGS(Z_BUFFER);
+
+ zjs_buffer_t *buffer = zjs_buffer_find(argv[0]);
+
+ // read == write means empty, leave one byte empty for full buffer
+ int read = tx_ring->read;
+ int write = tx_ring->write;
+
+ int copied = 0;
+ int size = buffer->bufsize;
+ if (write >= read) {
+ // first write to RINGMAX
+ int len = RINGMAX - write;
+ if (read == 0) {
+ --len;
+ }
+
+ if (size < len) {
+ len = size;
+ }
+ memcpy(&tx_ring->buf[write], buffer->buffer, len);
+ copied = len;
+ write += len;
+ if (write == RINGMAX) {
+ write = 0;
+ }
+ }
+ if (size > copied && write < read - 1) {
+ int len = read - 1 - write;
+ if (size < len) {
+ len = size;
+ }
+ memcpy(&tx_ring->buf[write + copied], buffer->buffer + copied, len);
+ copied += len;
+ write += len;
+ }
+ tx_ring->write = write;
+
+ if (copied > 0) {
+ struct device *dev = device_get_binding(WEBUSB_SERIAL_PORT_NAME);
+ uart_irq_tx_enable(dev);
+ // FIXME: supposedly this is bad to call uart from outside ISR
+ fill_uart_from_tx(dev);
+ }
+ if (size > copied) {
+ return zjs_error("output buffer full");
+ }
+
+ return ZJS_UNDEFINED;
+}
+
+typedef enum {
+ UART_STATE_DTR_WAIT,
+ UART_STATE_BAUDRATE_WAIT,
+ UART_STATE_READY
+} uart_state_t;
+static uart_state_t uart_state = UART_STATE_DTR_WAIT;
+
+static void check_uart(struct k_timer *timer)
+{
+ static int count = 0;
+ int rval;
+ u32_t result = 0;
+
+ struct device *dev = device_get_binding(WEBUSB_SERIAL_PORT_NAME);
+ if (!dev) {
+ ERR_PRINT("serial port not found\n");
+ k_timer_stop(timer);
+ return;
+ }
+
+ switch (uart_state) {
+ case UART_STATE_DTR_WAIT:
+ uart_line_ctrl_get(dev, LINE_CTRL_DTR, &result);
+ if (result) {
+ uart_state = UART_STATE_BAUDRATE_WAIT;
+ count = 0;
+ }
+ break;
+
+ case UART_STATE_BAUDRATE_WAIT:
+ rval = uart_line_ctrl_get(dev, LINE_CTRL_BAUD_RATE, &result);
+ if (rval) {
+ // failed to get baudrate, wait up to five seconds
+ if (++count >= 50) {
+ ERR_PRINT("failed to get baudrate\n");
+ k_timer_stop(timer);
+ }
+ }
+ else {
+ DBG_PRINT("baudrate: %d\n", result);
+ // apparently we don't actually need to use the baudrate, just need
+ // to make sure one has been set?
+ uart_state = UART_STATE_READY;
+ k_timer_stop(timer);
+ uart_irq_tx_disable(dev);
+ uart_irq_rx_disable(dev);
+ uart_irq_callback_set(dev, uart_interrupt_handler);
+ uart_irq_rx_enable(dev);
+ }
+ break;
+
+ default:
+ // only valid state left is ready, and timer shouldn't fire
+ ZJS_ASSERT(false, "unexpected state");
+ break;
+ }
+}
+
jerry_value_t zjs_webusb_init()
{
if (webusb_api) {
@@ -224,21 +547,43 @@ jerry_value_t zjs_webusb_init()
}
// create WebUSB object
- webusb_api = zjs_create_object();
+ ZVAL prototype = zjs_create_object();
zjs_native_func_t array[] = {
{ zjs_webusb_set_url, "setURL" },
+ { zjs_webusb_write, "write" },
{ NULL, NULL }
};
- zjs_obj_add_functions(webusb_api, array);
+ zjs_obj_add_functions(prototype, array);
+
+ webusb_api = zjs_create_object();
+ zjs_make_emitter(webusb_api, prototype, NULL, NULL);
// set the custom and vendor request handlers
webusb_register_request_handlers(&req_handlers);
+ k_timer_init(&uart_timer, check_uart, NULL);
+ k_timer_start(&uart_timer, 0, 100);
+
+ tx_ring = zjs_malloc(sizeof(ringbuf_t));
+ rx_ring = zjs_malloc(sizeof(ringbuf_t));
+ if (!tx_ring || !rx_ring) {
+ zjs_free(tx_ring);
+ zjs_free(rx_ring);
+ tx_ring = rx_ring = NULL;
+ return zjs_error_context("out of memory", 0, 0);
+ }
+
+ memset(tx_ring, 0, sizeof(ringbuf_t));
+ memset(rx_ring, 0, sizeof(ringbuf_t));
+
return jerry_acquire_value(webusb_api);
}
void zjs_webusb_cleanup()
{
+ zjs_free(tx_ring);
+ zjs_free(rx_ring);
+ tx_ring = rx_ring = NULL;
jerry_release_value(webusb_api);
webusb_api = 0;
zjs_free(webusb_origin_url);
diff --git a/src/zjs_webusb.json b/src/zjs_webusb.json
index 3d08d98..0088a24 100644
--- a/src/zjs_webusb.json
+++ b/src/zjs_webusb.json
@@ -2,6 +2,7 @@
"module": "webusb",
"description": "Module to advertise as a WebUSB device (only A101 has a USB driver in Zephyr currently)",
"require": "webusb",
+ "depends": ["buffer", "events"],
"targets": ["arduino_101"],
"zephyr_conf": {
"all": [