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