Commit 4b7bdf25 authored by Sudarsana Nagineni's avatar Sudarsana Nagineni Committed by Anas Nashif
Browse files

usb: Add WebUSB enabled custom class support.

This implements a modified version of CDC ACM class driver
in a WebUSB compatible way. It adds the WebUSB descriptors,
custom and vendor requests handlers so that the host OS and
browsers can get the required BOS descriptor and supported
origins from the device. It also adds a custom interface
class so that the interface will not be claimed by the host
CDC ACM driver.

A simple echo app also included in this commit to demonstrate
how to create and use a WebUSB interface, as well as the
communication between browser and WebUSB enabled device.

WebUSB Spec: https://wicg.github.io/webusb/



Origin: Based on CDC ACM device class driver in Zephyr

Jira: ZEP-744

Change-Id: I2eac10bd718e8fce35cda52e7c2ac425c3210e23
Signed-off-by: default avatarSudarsana Nagineni <sudarsana.nagineni@intel.com>
parent 120bc132
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
CONF_FILE = prj.conf
BOARD ?= arduino_101

include $(ZEPHYR_BASE)/Makefile.inc
+86 −0
Original line number Diff line number Diff line
Title: WebUSB Enabled Custom Driver and A Sample Application

This project includes:

1. WebUSB Enabled Custom Driver:
--------------------------------

This enables WebUSB support to the device via the platform capability
descriptors.

For a deeper dive into the WebUSB, refer to
https://github.com/WICG/webusb/blob/gh-pages/explainer.md

WebUSB API Specification:
https://wicg.github.io/webusb/

2. Sample Application:
----------------------

A simple echo application to demonstrate the WebUSB enabled custom
class driver.

This application receives the data and echoed back to the WebUSB
based web application (web page) running in the browser at host.

This application is intended for testing purposes only. For running
real usecase, implement applications based on the WebUSB API.

Building and flashing:
----------------------

Refer to https://wiki.zephyrproject.org/view/Arduino_101 for details on
building and flashing the image into an Arduino 101.

Testing with latest Google Chrome on host
-----------------------------------------

This sample application requires latest Google Chrome, a web page
based on WebUSB API to connect to the USB device and http server
running on localhost to serve the web page.

WebUSB is a powerful new feature added to the Web and it is available
only to secure origins. This means the web page/site that used to
connect to the device must be served over a secure connection (HTTPS).

For testing and development purposes, there is a flag in Chrome
(--disable-webusb-security) that disables this CORS-like checks for
origins and allow any origin to ask the user for permission to connect
to a device. So, we use this flag to interact with the device through
http://localhost by starting up http server on host and serving the
web page.

Follow below steps to run the demo on host:

1. Run the latest dev-channel release of Google Chrome on host.
   https://www.google.com/chrome/browser/desktop/index.html?extra=devchannel

2. Enable "Experimental Web Platform Features" flag in
   chrome://flags/#enable-experimental-web-platform-features.

3. Run chrome with the --disable-webusb-security switch to disable
   WebUSB's CORS-like checks for origin device communication.

4. Implement a web app (web page) using WebUSB API and run
   it on localhost.

   See the sample at https://github.com/nagineni/webusb-serial

   This sample web page demonstrate how to create and use a WebUSB
   interface, as well as demonstrate the communication between browser
   and WebUSB enabled device.

   To host the demo page locally: Clone the repo and start a web server
   in the appropriate directory.
   $ python -m SimpleHTTPServer

5. Connect the board to a host.

6. Once the device is booted, you should see a notification from
   Chrome: "Go to localhost to connect.". Click on the notification
   to open demo page.

7. Click on Connect button to connect to the device.

8. Send some text to the device by clicking on Send button. The demo app
   will receive the same text from the device and display it in the textarea.
+11 −0
Original line number Diff line number Diff line
CONFIG_STDOUT_CONSOLE=y
CONFIG_GPIO=y
CONFIG_ARC_INIT=n
CONFIG_USB=y
CONFIG_USB_DW=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_SYS_LOG_USB_DW_LEVEL=0
CONFIG_SYS_LOG_USB_LEVEL=0
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y
+3 −0
Original line number Diff line number Diff line
ccflags-y += -I${ZEPHYR_BASE}/include/usb/ -I${ZEPHYR_BASE}/subsys/usb/class

obj-y += webusb_serial.o main.o
+231 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2016 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file
 * @brief Sample app for WebUSB enabled custom class driver.
 *
 * Sample app for WebUSB enabled custom class driver. The received
 * data is echoed back to the WebUSB based application running in
 * the browser at host.
 */

#include <stdio.h>
#include <string.h>
#include <device.h>
#include <uart.h>
#include <zephyr.h>
#include "webusb_serial.h"

static volatile bool data_transmitted;
static volatile bool data_arrived;
static uint8_t data_buf[64];

/* WebUSB Platform Capability Descriptor */
static const uint8_t webusb_bos_descriptor[] = {
	/* Binary Object Store descriptor */
	0x05, 0x0F, 0x1D, 0x00, 0x01,

	/* WebUSB Platform Capability Descriptor:
	 * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
	 */
	0x18, 0x10, 0x05, 0x00,

	/* PlatformCapability UUID */
	0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47,
	0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65,

	/* Version, VendorCode, Landing Page */
	0x00, 0x01, 0x01, 0x01,
};

/* WebUSB Device Requests */
static const uint8_t webusb_allowed_origins[] = {
	/* Allowed Origins Header:
	 * https://wicg.github.io/webusb/#get-allowed-origins
	 */
	0x05, 0x00, 0x0D, 0x00, 0x01,

	/* Configuration Subset Header:
	 * https://wicg.github.io/webusb/#configuration-subset-header
	 */
	0x04, 0x01, 0x01, 0x01,

	/* Function Subset Header:
	 * https://wicg.github.io/webusb/#function-subset-header
	 */
	0x04, 0x02, 0x02, 0x01
};

/* Number of allowed origins */
#define NUMBER_OF_ALLOWED_ORIGINS   1

/* URL Descriptor: https://wicg.github.io/webusb/#url-descriptor */
static const uint8_t webusb_origin_url[] = {
	/* Length, DescriptorType, Scheme */
	0x11, 0x03, 0x00,
	'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', ':', '8', '0', '0', '0'
};

/**
 * @brief Custom handler for standard requests in
 *        order to catch the request and return the
 *        WebUSB Platform Capability Descriptor.
 *
 * @param pSetup    Information about the request to execute.
 * @param len       Size of the buffer.
 * @param data      Buffer containing the request result.
 *
 * @return  0 on success, negative errno code on fail
 */
int custom_handle_req(struct usb_setup_packet *pSetup,
		int32_t *len, uint8_t **data)
{
	if (GET_DESC_TYPE(pSetup->wValue) == DESCRIPTOR_TYPE_BOS) {
		*data = (uint8_t *)(&webusb_bos_descriptor);
		*len = sizeof(webusb_bos_descriptor);

		return 0;
	}

	return -ENOTSUP;
}

/**
 * @brief Handler called for WebUSB vendor specific commands.
 *
 * @param pSetup    Information about the request to execute.
 * @param len       Size of the buffer.
 * @param data      Buffer containing the request result.
 *
 * @return  0 on success, negative errno code on fail.
 */
int vendor_handle_req(struct usb_setup_packet *pSetup,
		int32_t *len, uint8_t **data)
{
	/* Get Allowed origins request */
	if (pSetup->bRequest == 0x01 && pSetup->wIndex == 0x01) {
		*data = (uint8_t *)(&webusb_allowed_origins);
		*len = sizeof(webusb_allowed_origins);

		return 0;
	} else if (pSetup->bRequest == 0x01 && pSetup->wIndex == 0x02) {
		/* Get URL request */
		uint8_t index = GET_DESC_INDEX(pSetup->wValue);

		if (index == 0 || index > NUMBER_OF_ALLOWED_ORIGINS)
			return -ENOTSUP;

		*data = (uint8_t *)(&webusb_origin_url);
		*len = sizeof(webusb_origin_url);

		return 0;
	}

	return -ENOTSUP;
}

static void interrupt_handler(struct device *dev)
{
	uart_irq_update(dev);

	if (uart_irq_tx_ready(dev)) {
		data_transmitted = true;
	}

	if (uart_irq_rx_ready(dev)) {
		data_arrived = true;
	}
}

static void write_data(struct device *dev, const char *buf, int len)
{
	uart_irq_tx_enable(dev);

	data_transmitted = false;
	uart_fifo_fill(dev, buf, len);
	while (data_transmitted == false)
		;

	uart_irq_tx_disable(dev);
}

static void read_and_echo_data(struct device *dev, int *bytes_read)
{
	while (data_arrived == false)
		;

	data_arrived = false;

	/* Read all data and echo it back */
	while ((*bytes_read = uart_fifo_read(dev,
	    data_buf, sizeof(data_buf)))) {
		write_data(dev, data_buf, *bytes_read);
	}
}

/* Custom and Vendor request handlers */
static struct webusb_req_handlers req_handlers = {
	.custom_handler = custom_handle_req,
	.vendor_handler = vendor_handle_req,
};

void main(void)
{
	struct device *dev;
	uint32_t baudrate, bytes_read, dtr = 0;
	int ret;

	dev = device_get_binding(WEBUSB_SERIAL_PORT_NAME);
	if (!dev) {
		printf("WebUSB device not found\n");
		return;
	}

	/* Set the custom and vendor request handlers */
	webusb_register_request_handlers(&req_handlers);

#ifdef CONFIG_UART_LINE_CTRL
	printf("Wait for DTR\n");
	while (1) {
		uart_line_ctrl_get(dev, LINE_CTRL_DTR, &dtr);
		if (dtr) {
			break;
		}
	}
	printf("DTR set, start test\n");

	/* Wait 1 sec for the host to do all settings */
	k_busy_wait(1000000);

	ret = uart_line_ctrl_get(dev, LINE_CTRL_BAUD_RATE, &baudrate);
	if (ret) {
		printf("Failed to get baudrate, ret code %d\n", ret);
	} else {
		printf("Baudrate detected: %d\n", baudrate);
	}
#endif

	uart_irq_callback_set(dev, interrupt_handler);

	/* Enable rx interrupts */
	uart_irq_rx_enable(dev);

	/* Echo the received data */
	while (1) {
		read_and_echo_data(dev, &bytes_read);
	}
}
Loading