1 /*
2  * Copyright (c) 2016-2019 Intel Corporation
3  * Copyright (c) 2023 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /**
9  * @file
10  * @brief Sample app for WebUSB enabled custom class driver.
11  *
12  * Sample app for WebUSB enabled custom class driver. The received
13  * data is echoed back to the WebUSB based application running in
14  * the browser at host.
15  */
16 
17 #define LOG_LEVEL CONFIG_USB_DEVICE_LOG_LEVEL
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(main);
20 
21 #include <zephyr/sys/byteorder.h>
22 #include <zephyr/usb/usb_device.h>
23 #include <zephyr/usb/bos.h>
24 #include <zephyr/usb/msos_desc.h>
25 
26 #include "webusb.h"
27 
28 /* random GUID {FA611CC3-7057-42EE-9D82-4919639562B3} */
29 #define WEBUSB_DEVICE_INTERFACE_GUID \
30 	'{', 0x00, 'F', 0x00, 'A', 0x00, '6', 0x00, '1', 0x00, '1', 0x00, \
31 	'C', 0x00, 'C', 0x00, '3', 0x00, '-', 0x00, '7', 0x00, '0', 0x00, \
32 	'5', 0x00, '7', 0x00, '-', 0x00, '4', 0x00, '2', 0x00, 'E', 0x00, \
33 	'E', 0x00, '-', 0x00, '9', 0x00, 'D', 0x00, '8', 0x00, '2', 0x00, \
34 	'-', 0x00, '4', 0x00, '9', 0x00, '1', 0x00, '9', 0x00, '6', 0x00, \
35 	'3', 0x00, '9', 0x00, '5', 0x00, '6', 0x00, '2', 0x00, 'B', 0x00, \
36 	'3', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00
37 
38 #define COMPATIBLE_ID_WINUSB \
39 	'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00
40 
41 static struct msosv2_descriptor_t {
42 	struct msosv2_descriptor_set_header header;
43 #if defined(CONFIG_USB_CDC_ACM)
44 	struct msosv2_function_subset_header subset_header;
45 #endif
46 	struct msosv2_compatible_id webusb_compatible_id;
47 	struct msosv2_guids_property webusb_guids_property;
48 } __packed msosv2_descriptor = {
49 	/* Microsoft OS 2.0 descriptor set
50 	 * This tells Windows what kind of device this is and to install the WinUSB driver.
51 	 */
52 	.header = {
53 		.wLength = sizeof(struct msosv2_descriptor_set_header),
54 		.wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR,
55 		.dwWindowsVersion = 0x06030000,
56 		.wTotalLength = sizeof(struct msosv2_descriptor_t),
57 	},
58 #if defined(CONFIG_USB_CDC_ACM)
59 	/* If CONFIG_USB_CDC_ACM is selected, extra interfaces will be added on build time,
60 	 * making the target a composite device, which requires an extra Function
61 	 * Subset Header.
62 	 */
63 	.subset_header = {
64 		.wLength = sizeof(struct msosv2_function_subset_header),
65 		.wDescriptorType = MS_OS_20_SUBSET_HEADER_FUNCTION,
66 		/* The WebUSB interface number becomes the first when CDC_ACM is enabled by
67 		 * configuration.  Beware that if this sample is used as an inspiration for
68 		 * applications, where the WebUSB interface is no longer the first,
69 		 * remember to adjust bFirstInterface.
70 		 */
71 		.bFirstInterface = 0,
72 		.wSubsetLength = 160
73 	},
74 #endif
75 	.webusb_compatible_id = {
76 		.wLength = sizeof(struct msosv2_compatible_id),
77 		.wDescriptorType = MS_OS_20_FEATURE_COMPATIBLE_ID,
78 		.CompatibleID = {COMPATIBLE_ID_WINUSB},
79 	},
80 	.webusb_guids_property = {
81 		.wLength = sizeof(struct msosv2_guids_property),
82 		.wDescriptorType = MS_OS_20_FEATURE_REG_PROPERTY,
83 		.wPropertyDataType = MS_OS_20_PROPERTY_DATA_REG_MULTI_SZ,
84 		.wPropertyNameLength = 42,
85 		.PropertyName = {DEVICE_INTERFACE_GUIDS_PROPERTY_NAME},
86 		.wPropertyDataLength = 80,
87 		.bPropertyData = {WEBUSB_DEVICE_INTERFACE_GUID},
88 	},
89 };
90 
91 USB_DEVICE_BOS_DESC_DEFINE_CAP struct usb_bos_webusb_desc {
92 	struct usb_bos_platform_descriptor platform;
93 	struct usb_bos_capability_webusb cap;
94 } __packed bos_cap_webusb = {
95 	/* WebUSB Platform Capability Descriptor:
96 	 * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
97 	 */
98 	.platform = {
99 		.bLength = sizeof(struct usb_bos_platform_descriptor)
100 			+ sizeof(struct usb_bos_capability_webusb),
101 		.bDescriptorType = USB_DESC_DEVICE_CAPABILITY,
102 		.bDevCapabilityType = USB_BOS_CAPABILITY_PLATFORM,
103 		.bReserved = 0,
104 		/* WebUSB Platform Capability UUID
105 		 * 3408b638-09a9-47a0-8bfd-a0768815b665
106 		 */
107 		.PlatformCapabilityUUID = {
108 			0x38, 0xB6, 0x08, 0x34,
109 			0xA9, 0x09,
110 			0xA0, 0x47,
111 			0x8B, 0xFD,
112 			0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65,
113 		},
114 	},
115 	.cap = {
116 		.bcdVersion = sys_cpu_to_le16(0x0100),
117 		.bVendorCode = 0x01,
118 		.iLandingPage = 0x01
119 	}
120 };
121 
122 USB_DEVICE_BOS_DESC_DEFINE_CAP struct usb_bos_msosv2_desc {
123 	struct usb_bos_platform_descriptor platform;
124 	struct usb_bos_capability_msos cap;
125 } __packed bos_cap_msosv2 = {
126 	/* Microsoft OS 2.0 Platform Capability Descriptor
127 	 * See https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/
128 	 * microsoft-defined-usb-descriptors
129 	 * Adapted from the source:
130 	 * https://github.com/sowbug/weblight/blob/master/firmware/webusb.c
131 	 * (BSD-2) Thanks http://janaxelson.com/files/ms_os_20_descriptors.c
132 	 */
133 	.platform = {
134 		.bLength = sizeof(struct usb_bos_platform_descriptor)
135 			+ sizeof(struct usb_bos_capability_msos),
136 		.bDescriptorType = USB_DESC_DEVICE_CAPABILITY,
137 		.bDevCapabilityType = USB_BOS_CAPABILITY_PLATFORM,
138 		.bReserved = 0,
139 		.PlatformCapabilityUUID = {
140 			/**
141 			 * MS OS 2.0 Platform Capability ID
142 			 * D8DD60DF-4589-4CC7-9CD2-659D9E648A9F
143 			 */
144 			0xDF, 0x60, 0xDD, 0xD8,
145 			0x89, 0x45,
146 			0xC7, 0x4C,
147 			0x9C, 0xD2,
148 			0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F,
149 		},
150 	},
151 	.cap = {
152 		/* Windows version (8.1) (0x06030000) */
153 		.dwWindowsVersion = sys_cpu_to_le32(0x06030000),
154 		.wMSOSDescriptorSetTotalLength =
155 			sys_cpu_to_le16(sizeof(msosv2_descriptor)),
156 		/* Arbitrary code that is used as bRequest for vendor command */
157 		.bMS_VendorCode = 0x02,
158 		.bAltEnumCode = 0x00
159 	},
160 };
161 
162 USB_DEVICE_BOS_DESC_DEFINE_CAP struct usb_bos_capability_lpm bos_cap_lpm = {
163 	.bLength = sizeof(struct usb_bos_capability_lpm),
164 	.bDescriptorType = USB_DESC_DEVICE_CAPABILITY,
165 	.bDevCapabilityType = USB_BOS_CAPABILITY_EXTENSION,
166 	/**
167 	 * Currently there is not a single device driver in Zephyr that supports
168 	 * LPM. Moreover, Zephyr USB stack does not have LPM support, so do not
169 	 * falsely claim to support LPM.
170 	 * BIT(1) - LPM support
171 	 * BIT(2) - BESL support
172 	 */
173 	.bmAttributes = 0,
174 };
175 
176 /* WebUSB Device Requests */
177 static const uint8_t webusb_allowed_origins[] = {
178 	/* Allowed Origins Header:
179 	 * https://wicg.github.io/webusb/#get-allowed-origins
180 	 */
181 	0x05, 0x00, 0x0D, 0x00, 0x01,
182 
183 	/* Configuration Subset Header:
184 	 * https://wicg.github.io/webusb/#configuration-subset-header
185 	 */
186 	0x04, 0x01, 0x01, 0x01,
187 
188 	/* Function Subset Header:
189 	 * https://wicg.github.io/webusb/#function-subset-header
190 	 */
191 	0x04, 0x02, 0x02, 0x01
192 };
193 
194 /* Number of allowed origins */
195 #define NUMBER_OF_ALLOWED_ORIGINS   1
196 
197 /* URL Descriptor: https://wicg.github.io/webusb/#url-descriptor */
198 static const uint8_t webusb_origin_url[] = {
199 	/* Length, DescriptorType, Scheme */
200 	0x11, 0x03, 0x00,
201 	'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', ':', '8', '0', '0', '0'
202 };
203 
204 /* Predefined response to control commands related to MS OS 1.0 descriptors
205  * Please note that this code only defines "extended compat ID OS feature
206  * descriptors" and not "extended properties OS features descriptors"
207  */
208 #define MSOS_STRING_LENGTH	18
209 static struct string_desc {
210 	uint8_t bLength;
211 	uint8_t bDescriptorType;
212 	uint8_t bString[MSOS_STRING_LENGTH];
213 
214 } __packed msos1_string_descriptor = {
215 	.bLength = MSOS_STRING_LENGTH,
216 	.bDescriptorType = USB_DESC_STRING,
217 	/* Signature MSFT100 */
218 	.bString = {
219 		'M', 0x00, 'S', 0x00, 'F', 0x00, 'T', 0x00,
220 		'1', 0x00, '0', 0x00, '0', 0x00,
221 		0x03, /* Vendor Code, used for a control request */
222 		0x00, /* Padding byte for VendorCode looks like UTF16 */
223 	},
224 };
225 
226 static const uint8_t msos1_compatid_descriptor[] = {
227 	/* See https://github.com/pbatard/libwdi/wiki/WCID-Devices */
228 	/* MS OS 1.0 header section */
229 	0x28, 0x00, 0x00, 0x00, /* Descriptor size (40 bytes)          */
230 	0x00, 0x01,             /* Version 1.00                        */
231 	0x04, 0x00,             /* Type: Extended compat ID descriptor */
232 	0x01,                   /* Number of function sections         */
233 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,       /* reserved    */
234 
235 	/* MS OS 1.0 function section */
236 	0x02,     /* Index of interface this section applies to. */
237 	0x01,     /* reserved */
238 	/* 8-byte compatible ID string, then 8-byte sub-compatible ID string */
239 	'W',  'I',  'N',  'U',  'S',  'B',  0x00, 0x00,
240 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
241 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* reserved */
242 };
243 
244 /**
245  * @brief Custom handler for standard requests in
246  *        order to catch the request and return the
247  *        WebUSB Platform Capability Descriptor.
248  *
249  * @param pSetup    Information about the request to execute.
250  * @param len       Size of the buffer.
251  * @param data      Buffer containing the request result.
252  *
253  * @return  0 on success, negative errno code on fail
254  */
custom_handle_req(struct usb_setup_packet * pSetup,int32_t * len,uint8_t ** data)255 int custom_handle_req(struct usb_setup_packet *pSetup,
256 		      int32_t *len, uint8_t **data)
257 {
258 	if (usb_reqtype_is_to_device(pSetup)) {
259 		return -ENOTSUP;
260 	}
261 
262 	if (USB_GET_DESCRIPTOR_TYPE(pSetup->wValue) == USB_DESC_STRING &&
263 	    USB_GET_DESCRIPTOR_INDEX(pSetup->wValue) == 0xEE) {
264 		*data = (uint8_t *)(&msos1_string_descriptor);
265 		*len = sizeof(msos1_string_descriptor);
266 
267 		LOG_DBG("Get MS OS Descriptor v1 string");
268 
269 		return 0;
270 	}
271 
272 	return -EINVAL;
273 }
274 
275 /**
276  * @brief Handler called for vendor specific commands. This includes
277  *        WebUSB allowed origins and MS OS 1.0 and 2.0 descriptors.
278  *
279  * @param pSetup    Information about the request to execute.
280  * @param len       Size of the buffer.
281  * @param data      Buffer containing the request result.
282  *
283  * @return  0 on success, negative errno code on fail.
284  */
vendor_handle_req(struct usb_setup_packet * pSetup,int32_t * len,uint8_t ** data)285 int vendor_handle_req(struct usb_setup_packet *pSetup,
286 		      int32_t *len, uint8_t **data)
287 {
288 	if (usb_reqtype_is_to_device(pSetup)) {
289 		return -ENOTSUP;
290 	}
291 
292 	/* Get Allowed origins request */
293 	if (pSetup->bRequest == 0x01 && pSetup->wIndex == 0x01) {
294 		*data = (uint8_t *)(&webusb_allowed_origins);
295 		*len = sizeof(webusb_allowed_origins);
296 
297 		LOG_DBG("Get webusb_allowed_origins");
298 
299 		return 0;
300 	} else if (pSetup->bRequest == 0x01 && pSetup->wIndex == 0x02) {
301 		/* Get URL request */
302 		uint8_t index = USB_GET_DESCRIPTOR_INDEX(pSetup->wValue);
303 
304 		if (index == 0U || index > NUMBER_OF_ALLOWED_ORIGINS) {
305 			return -ENOTSUP;
306 		}
307 
308 		*data = (uint8_t *)(&webusb_origin_url);
309 		*len = sizeof(webusb_origin_url);
310 
311 		LOG_DBG("Get webusb_origin_url");
312 
313 		return 0;
314 	} else if (pSetup->bRequest == bos_cap_msosv2.cap.bMS_VendorCode &&
315 		   pSetup->wIndex == MS_OS_20_DESCRIPTOR_INDEX) {
316 		/* Get MS OS 2.0 Descriptors request */
317 		*data = (uint8_t *)(&msosv2_descriptor);
318 		*len = sizeof(msosv2_descriptor);
319 
320 		LOG_DBG("Get MS OS Descriptors v2");
321 
322 		return 0;
323 	} else if (pSetup->bRequest == 0x03 && pSetup->wIndex == 0x04) {
324 		/* Get MS OS 1.0 Descriptors request */
325 		/* 0x04 means "Extended compat ID".
326 		 * Use 0x05 instead for "Extended properties".
327 		 */
328 		*data = (uint8_t *)(&msos1_compatid_descriptor);
329 		*len = sizeof(msos1_compatid_descriptor);
330 
331 		LOG_DBG("Get MS OS Descriptors CompatibleID");
332 
333 		return 0;
334 	}
335 
336 	return -ENOTSUP;
337 }
338 
339 /* Custom and Vendor request handlers */
340 static struct webusb_req_handlers req_handlers = {
341 	.custom_handler = custom_handle_req,
342 	.vendor_handler = vendor_handle_req,
343 };
344 
main(void)345 int main(void)
346 {
347 	int ret;
348 
349 	LOG_DBG("");
350 
351 	usb_bos_register_cap((void *)&bos_cap_webusb);
352 	usb_bos_register_cap((void *)&bos_cap_msosv2);
353 	usb_bos_register_cap((void *)&bos_cap_lpm);
354 
355 	/* Set the custom and vendor request handlers */
356 	webusb_register_request_handlers(&req_handlers);
357 
358 	ret = usb_enable(NULL);
359 	if (ret != 0) {
360 		LOG_ERR("Failed to enable USB");
361 		return 0;
362 	}
363 	return 0;
364 }
365