xref: /illumos-gate/usr/src/uts/common/io/usb/usba/usba_bos.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2019 Joyent, Inc.
14  */
15 
16 /*
17  * Routines to access, parse, and manage the USB Binary Object Store
18  */
19 
20 #define	USBA_FRAMEWORK
21 #include <sys/usb/usba/usba_impl.h>
22 #include <sys/strsun.h>
23 #include <sys/sysmacros.h>
24 
25 static size_t
26 usba_bos_parse_bos_descr(const uchar_t *buf, size_t buflen,
27     usb_bos_descr_t *bosp, size_t rlen)
28 {
29 	if (buf == NULL || bosp == NULL || buflen < USB_BOS_PACKED_SIZE ||
30 	    buf[1] != USB_DESCR_TYPE_BOS) {
31 		return (USB_PARSE_ERROR);
32 	}
33 
34 	return (usb_parse_data("ccsc", buf, buflen, bosp, rlen));
35 }
36 
37 static boolean_t
38 usba_bos_parse_usb2ext(const uchar_t *buf, size_t buflen, usb_bos_t *bosp)
39 {
40 	size_t len;
41 
42 	if (buflen != USB_BOS_USB2EXT_PACKED_SIZE) {
43 		return (B_FALSE);
44 	}
45 
46 	len = usb_parse_data("cccl", buf, buflen, &bosp->ubos_caps.ubos_usb2,
47 	    sizeof (usb_bos_usb2ext_t));
48 	return (len == sizeof (usb_bos_usb2ext_t));
49 }
50 
51 static boolean_t
52 usba_bos_parse_superspeed(const uchar_t *buf, size_t buflen, usb_bos_t *bosp)
53 {
54 	size_t len;
55 
56 	if (buflen != USB_BOS_SSUSB_PACKED_SIZE) {
57 		return (B_FALSE);
58 	}
59 
60 	len = usb_parse_data("ccccsccs", buf, buflen,
61 	    &bosp->ubos_caps.ubos_ssusb, sizeof (usb_bos_ssusb_t));
62 	return (len == sizeof (usb_bos_ssusb_t));
63 }
64 
65 static boolean_t
66 usba_bos_parse_container(const uchar_t *buf, size_t buflen, usb_bos_t *bosp)
67 {
68 	size_t len;
69 
70 	if (buflen != USB_BOS_CONTAINER_PACKED_SIZE) {
71 		return (B_FALSE);
72 	}
73 
74 	len = usb_parse_data("cccc16c", buf, buflen,
75 	    &bosp->ubos_caps.ubos_container, sizeof (usb_bos_container_t));
76 	return (len == sizeof (usb_bos_container_t));
77 }
78 
79 static boolean_t
80 usba_bos_parse_precision_time(const uchar_t *buf, size_t buflen,
81     usb_bos_t *bosp)
82 {
83 	size_t len;
84 
85 	if (buflen != USB_BOS_PRECISION_TIME_PACKED_SIZE) {
86 		return (B_FALSE);
87 	}
88 
89 	len = usb_parse_data("ccc", buf, buflen, &bosp->ubos_caps.ubos_time,
90 	    sizeof (usb_bos_precision_time_t));
91 	/*
92 	 * The actual size of this structure will usually be rounded up to four
93 	 * bytes by the compiler, therefore we need to compare against the
94 	 * packed size.
95 	 */
96 	return (len == USB_BOS_PRECISION_TIME_PACKED_SIZE);
97 }
98 
99 /*
100  * Validate that the BOS looks reasonable. This means the following:
101  *
102  * - We read the whole length of the descriptor
103  * - The total number of capabilities doesn't exceed the expected value
104  * - The length of each device capabilities fits within our expected range
105  *
106  * After we finish that up, go through and save all of the valid BOS
107  * descriptors, unpacking the ones that we actually understand.
108  */
109 static boolean_t
110 usba_bos_save(usba_device_t *ud, const mblk_t *mp, usb_bos_descr_t *bdesc)
111 {
112 	size_t len = MBLKL(mp);
113 	const uchar_t *buf = mp->b_rptr;
114 	uint_t ncaps, nalloc;
115 	usb_bos_t *bos;
116 
117 	if (bdesc->bLength != USB_BOS_PACKED_SIZE ||
118 	    bdesc->bNumDeviceCaps == 0 || len < USB_BOS_PACKED_SIZE ||
119 	    len < bdesc->wTotalLength) {
120 		return (B_FALSE);
121 	}
122 
123 	len = MIN(len, bdesc->wTotalLength);
124 	buf += USB_BOS_PACKED_SIZE;
125 	len -= USB_BOS_PACKED_SIZE;
126 
127 	if (len < USB_DEV_CAP_PACKED_SIZE) {
128 		return (B_FALSE);
129 	}
130 
131 	ncaps = 0;
132 	while (len > 0) {
133 		usb_dev_cap_descr_t dev;
134 
135 		if (usb_parse_data("ccc", buf, len, &dev, sizeof (dev)) !=
136 		    USB_DEV_CAP_PACKED_SIZE) {
137 			return (B_FALSE);
138 		}
139 
140 		if (dev.bDescriptorType != USB_DESCR_TYPE_DEV_CAPABILITY ||
141 		    dev.bLength > len) {
142 			return (B_FALSE);
143 		}
144 
145 		ncaps++;
146 		len -= dev.bLength;
147 		buf += dev.bLength;
148 	}
149 
150 	if (ncaps != bdesc->bNumDeviceCaps) {
151 		return (B_FALSE);
152 	}
153 
154 	nalloc = ncaps;
155 	bos = kmem_zalloc(sizeof (usb_bos_t) * nalloc, KM_SLEEP);
156 	buf = mp->b_rptr + USB_BOS_PACKED_SIZE;
157 	len = MIN(MBLKL(mp), bdesc->wTotalLength) - USB_BOS_PACKED_SIZE;
158 	ncaps = 0;
159 	while (len > 0) {
160 		usb_dev_cap_descr_t dev;
161 		boolean_t valid;
162 
163 		if (usb_parse_data("ccc", buf, len, &dev, sizeof (dev)) !=
164 		    USB_DEV_CAP_PACKED_SIZE) {
165 			goto fail;
166 		}
167 
168 		bos[ncaps].ubos_length = dev.bLength;
169 		bos[ncaps].ubos_type = dev.bDevCapabilityType;
170 
171 		valid = B_FALSE;
172 		switch (dev.bDevCapabilityType) {
173 		case USB_BOS_TYPE_USB2_EXT:
174 			valid = usba_bos_parse_usb2ext(buf, dev.bLength,
175 			    &bos[ncaps]);
176 			break;
177 		case USB_BOS_TYPE_SUPERSPEED:
178 			valid = usba_bos_parse_superspeed(buf, dev.bLength,
179 			    &bos[ncaps]);
180 			break;
181 		case USB_BOS_TYPE_CONTAINER:
182 			valid = usba_bos_parse_container(buf, dev.bLength,
183 			    &bos[ncaps]);
184 			break;
185 		case USB_BOS_TYPE_PRECISION_TIME:
186 			valid = usba_bos_parse_precision_time(buf, dev.bLength,
187 			    &bos[ncaps]);
188 			break;
189 		default:
190 			/*
191 			 * Override the type to one that we know isn't used to
192 			 * indicate that the caller can't rely on the type
193 			 * that's present here.
194 			 */
195 			bos[ncaps].ubos_type = USB_BOS_TYPE_INVALID;
196 			bcopy(buf, bos[ncaps].ubos_caps.ubos_raw, dev.bLength);
197 			valid = B_TRUE;
198 			break;
199 		}
200 
201 		if (valid) {
202 			ncaps++;
203 		} else {
204 			bos[ncaps].ubos_length = 0;
205 			bos[ncaps].ubos_type = USB_BOS_TYPE_INVALID;
206 			bzero(bos[ncaps].ubos_caps.ubos_raw,
207 			    sizeof (bos[ncaps].ubos_caps.ubos_raw));
208 		}
209 		len -= dev.bLength;
210 		buf += dev.bLength;
211 	}
212 
213 	ud->usb_bos_nalloc = nalloc;
214 	ud->usb_bos_nents = ncaps;
215 	ud->usb_bos = bos;
216 
217 	return (B_TRUE);
218 
219 fail:
220 	kmem_free(bos, sizeof (usb_bos_t) * nalloc);
221 	return (B_FALSE);
222 }
223 
224 /*
225  * Read the Binary Object Store (BOS) data from the device and attempt to parse
226  * it. Do not fail to attach the device if we cannot get all of the information
227  * at this time. While certain aspects of the BOS are required for Windows,
228  * which suggests that we could actually rely on it, we haven't historically.
229  */
230 void
231 usba_get_binary_object_store(dev_info_t *dip, usba_device_t *ud)
232 {
233 	int			rval;
234 	mblk_t			*mp = NULL;
235 	usb_cr_t		completion_reason;
236 	usb_cb_flags_t		cb_flags;
237 	usb_pipe_handle_t	ph;
238 	size_t			size;
239 	usb_bos_descr_t		bos;
240 
241 	/*
242 	 * The BOS is only supported on USB 3.x devices. Therefore if the bcdUSB
243 	 * is greater than USB 2.0, we can check this. Note, USB 3.x devices
244 	 * that are linked on a USB device will report version 2.1 in the bcdUSB
245 	 * field.
246 	 */
247 	if (ud->usb_dev_descr->bcdUSB <= 0x200) {
248 		return;
249 	}
250 
251 	ph = usba_get_dflt_pipe_handle(dip);
252 
253 	/*
254 	 * First get just the BOS descriptor itself.
255 	 */
256 	rval = usb_pipe_sync_ctrl_xfer(dip, ph,
257 	    USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_TYPE_STANDARD,
258 	    USB_REQ_GET_DESCR,			/* bRequest */
259 	    (USB_DESCR_TYPE_BOS << 8),		/* wValue */
260 	    0,					/* wIndex */
261 	    USB_BOS_PACKED_SIZE,		/* wLength */
262 	    &mp, USB_ATTRS_SHORT_XFER_OK,
263 	    &completion_reason, &cb_flags, 0);
264 
265 	if (rval != USB_SUCCESS) {
266 		return;
267 	}
268 
269 	size = usba_bos_parse_bos_descr(mp->b_rptr, MBLKL(mp), &bos,
270 	    sizeof (bos));
271 	freemsg(mp);
272 	mp = NULL;
273 	if (size < USB_BOS_PACKED_SIZE) {
274 		return;
275 	}
276 
277 	/*
278 	 * Check to see if there are any capabilities and if it's worth getting
279 	 * the whole BOS.
280 	 */
281 	if (bos.bLength != USB_BOS_PACKED_SIZE || bos.bNumDeviceCaps == 0) {
282 		return;
283 	}
284 
285 	rval = usb_pipe_sync_ctrl_xfer(dip, ph,
286 	    USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_TYPE_STANDARD,
287 	    USB_REQ_GET_DESCR,			/* bRequest */
288 	    (USB_DESCR_TYPE_BOS << 8),		/* wValue */
289 	    0,					/* wIndex */
290 	    bos.wTotalLength,			/* wLength */
291 	    &mp, USB_ATTRS_SHORT_XFER_OK,
292 	    &completion_reason, &cb_flags, 0);
293 
294 	if (rval != USB_SUCCESS) {
295 		return;
296 	}
297 
298 	size = usba_bos_parse_bos_descr(mp->b_rptr, MBLKL(mp), &bos,
299 	    sizeof (bos));
300 	if (size < USB_BOS_PACKED_SIZE) {
301 		freemsg(mp);
302 		return;
303 	}
304 
305 	if (!usba_bos_save(ud, mp, &bos)) {
306 		freemsg(mp);
307 		return;
308 	}
309 
310 	ud->usb_bos_mp = mp;
311 }
312 
313 static void
314 usba_add_superspeed_props(dev_info_t *dip, usb_bos_ssusb_t *ssusb)
315 {
316 	char *supported[4];
317 	uint_t nsup = 0;
318 	char *min;
319 
320 	if (ssusb->wSpeedsSupported & USB_BOS_SSUSB_SPEED_LOW) {
321 		supported[nsup++] = "low-speed";
322 	}
323 
324 	if (ssusb->wSpeedsSupported & USB_BOS_SSUSB_SPEED_FULL) {
325 		supported[nsup++] = "full-speed";
326 	}
327 
328 	if (ssusb->wSpeedsSupported & USB_BOS_SSUSB_SPEED_HIGH) {
329 		supported[nsup++] = "high-speed";
330 	}
331 
332 	if (ssusb->wSpeedsSupported & USB_BOS_SSUSB_SPEED_SUPER) {
333 		supported[nsup++] = "super-speed";
334 	}
335 
336 	if (nsup != 0 && ndi_prop_update_string_array(DDI_DEV_T_NONE, dip,
337 	    "usb-supported-speeds", supported, nsup) != DDI_PROP_SUCCESS) {
338 		USB_DPRINTF_L2(DPRINT_MASK_USBA, NULL, "failed to add "
339 		    "usb-supported-speeds property");
340 	}
341 
342 	switch (ssusb->bFunctionalitySupport) {
343 	case 0:
344 		min = "low-speed";
345 		break;
346 	case 1:
347 		min = "full-speed";
348 		break;
349 	case 2:
350 		min = "high-speed";
351 		break;
352 	case 3:
353 		min = "super-speed";
354 		break;
355 	default:
356 		min = NULL;
357 	}
358 
359 	if (min != NULL && ndi_prop_update_string(DDI_DEV_T_NONE, dip,
360 	    "usb-minimum-speed", min) != DDI_PROP_SUCCESS) {
361 		USB_DPRINTF_L2(DPRINT_MASK_USBA, NULL, "failed to add "
362 		    "usb-minimum-speed property");
363 	}
364 }
365 
366 static void
367 usba_add_container_props(dev_info_t *dip, usb_bos_container_t *cp)
368 {
369 	if (ndi_prop_update_byte_array(DDI_DEV_T_NONE, dip, "usb-container-id",
370 	    cp->ContainerId, sizeof (cp->ContainerId)) != DDI_PROP_SUCCESS) {
371 		USB_DPRINTF_L2(DPRINT_MASK_USBA, NULL, "failed to add "
372 		    "usb-container-id property");
373 	}
374 }
375 
376 void
377 usba_add_binary_object_store_props(dev_info_t *dip, usba_device_t *ud)
378 {
379 	uint_t i;
380 
381 	if (ud->usb_bos == NULL) {
382 		return;
383 	}
384 
385 	for (i = 0; i < ud->usb_bos_nents; i++) {
386 		usb_bos_t *bos = &ud->usb_bos[i];
387 
388 		switch (bos->ubos_type) {
389 		case USB_BOS_TYPE_SUPERSPEED:
390 			usba_add_superspeed_props(dip,
391 			    &bos->ubos_caps.ubos_ssusb);
392 			break;
393 		case USB_BOS_TYPE_CONTAINER:
394 			usba_add_container_props(dip,
395 			    &bos->ubos_caps.ubos_container);
396 			break;
397 		default:
398 			/*
399 			 * This is a capability that we're not going to add
400 			 * devinfo properties to describe.
401 			 */
402 			continue;
403 		}
404 	}
405 }
406 
407 void
408 usba_free_binary_object_store(usba_device_t *ud)
409 {
410 	if (ud->usb_bos_mp != NULL) {
411 		freemsg(ud->usb_bos_mp);
412 		ud->usb_bos_mp = NULL;
413 	}
414 
415 	if (ud->usb_bos != NULL) {
416 		kmem_free(ud->usb_bos, sizeof (usb_bos_t) * ud->usb_bos_nalloc);
417 		ud->usb_bos = NULL;
418 		ud->usb_bos_nalloc = ud->usb_bos_nents = 0;
419 	}
420 }
421