diff options
author | Geoff Gustafson <geoff@linux.intel.com> | 2017-10-13 08:07:35 -0700 |
---|---|---|
committer | Jimmy Huang <jimmy.huang@linux.intel.com> | 2017-10-19 13:59:35 -0700 |
commit | e29b70002e0610946b14de2fe875d8ababc2817b (patch) | |
tree | 20473055e8af95a138124079ada7e4ea8ca2f7cb | |
parent | e6c97523c082b5d1db6a56b3fdcb3b10a81e6a34 (diff) |
[webusb] Add UART support and data APIs to WebUSB
Signed-off-by: Geoff Gustafson <geoff@linux.intel.com>
-rw-r--r-- | docs/uart.md | 2 | ||||
-rw-r--r-- | docs/webusb.md | 16 | ||||
-rw-r--r-- | samples/UART.js | 7 | ||||
-rw-r--r-- | samples/WebUSB.js | 28 | ||||
-rw-r--r-- | src/zjs_webusb.c | 349 | ||||
-rw-r--r-- | src/zjs_webusb.json | 1 |
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": [ |