source: mainline/uspace/lib/usb/src/recognise.c@ 51f0e410

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 51f0e410 was 51f0e410, checked in by Vojtech Horky <vojtechhorky@…>, 14 years ago

Interface-based matches use also device descriptor

This way it is possible to distinguish generic class drivers and
class drivers for specific device (it allows to mix these drivers
when MID is in use).

  • Property mode set to 100644
File size: 14.4 KB
Line 
1/*
2 * Copyright (c) 2010 Vojtech Horky
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libusb
30 * @{
31 */
32/** @file
33 * @brief Functions for recognising kind of attached devices.
34 */
35#include <sys/types.h>
36#include <usb/usbdrv.h>
37#include <usb/pipes.h>
38#include <usb/recognise.h>
39#include <usb/ddfiface.h>
40#include <usb/request.h>
41#include <usb/classes/classes.h>
42#include <stdio.h>
43#include <errno.h>
44
45static size_t device_name_index = 0;
46static FIBRIL_MUTEX_INITIALIZE(device_name_index_mutex);
47
48device_ops_t child_ops = {
49 .interfaces[USB_DEV_IFACE] = &usb_iface_hub_child_impl
50};
51
52#define BCD_INT(a) (((unsigned int)(a)) / 256)
53#define BCD_FRAC(a) (((unsigned int)(a)) % 256)
54
55#define BCD_FMT "%x.%x"
56#define BCD_ARGS(a) BCD_INT((a)), BCD_FRAC((a))
57
58/* FIXME: make this dynamic */
59#define MATCH_STRING_MAX 256
60
61/** Add formatted match id.
62 *
63 * @param matches List of match ids where to add to.
64 * @param score Score of the match.
65 * @param format Printf-like format
66 * @return Error code.
67 */
68static int usb_add_match_id(match_id_list_t *matches, int score,
69 const char *format, ...)
70{
71 char *match_str = NULL;
72 match_id_t *match_id = NULL;
73 int rc;
74
75 match_str = malloc(MATCH_STRING_MAX + 1);
76 if (match_str == NULL) {
77 rc = ENOMEM;
78 goto failure;
79 }
80
81 /*
82 * FIXME: replace with dynamic allocation of exact size
83 */
84 va_list args;
85 va_start(args, format );
86 vsnprintf(match_str, MATCH_STRING_MAX, format, args);
87 match_str[MATCH_STRING_MAX] = 0;
88 va_end(args);
89
90 match_id = create_match_id();
91 if (match_id == NULL) {
92 rc = ENOMEM;
93 goto failure;
94 }
95
96 match_id->id = match_str;
97 match_id->score = score;
98 add_match_id(matches, match_id);
99
100 return EOK;
101
102failure:
103 if (match_str != NULL) {
104 free(match_str);
105 }
106 if (match_id != NULL) {
107 match_id->id = NULL;
108 delete_match_id(match_id);
109 }
110
111 return rc;
112}
113
114#define ADD_MATCHID_OR_RETURN(match_ids, score, format, ...) \
115 do { \
116 int __rc = usb_add_match_id((match_ids), (score), \
117 format, ##__VA_ARGS__); \
118 if (__rc != EOK) { \
119 return __rc; \
120 } \
121 } while (0)
122
123/** Create device match ids based on its interface.
124 *
125 * @param[in] descriptor Interface descriptor.
126 * @param[out] matches Initialized list of match ids.
127 * @return Error code (the two mentioned are not the only ones).
128 * @retval EINVAL Invalid input parameters (expects non NULL pointers).
129 * @retval ENOENT Interface does not specify class.
130 */
131int usb_device_create_match_ids_from_interface(
132 const usb_standard_device_descriptor_t *desc_device,
133 const usb_standard_interface_descriptor_t *desc_interface,
134 match_id_list_t *matches)
135{
136 if (desc_interface == NULL) {
137 return EINVAL;
138 }
139 if (matches == NULL) {
140 return EINVAL;
141 }
142
143 if (desc_interface->interface_class == USB_CLASS_USE_INTERFACE) {
144 return ENOENT;
145 }
146
147 const char *classname = usb_str_class(desc_interface->interface_class);
148 assert(classname != NULL);
149
150#define IFACE_PROTOCOL_FMT "interface&class=%s&subclass=0x%02x&protocol=0x%02x"
151#define IFACE_PROTOCOL_ARGS classname, desc_interface->interface_subclass, \
152 desc_interface->interface_protocol
153
154#define IFACE_SUBCLASS_FMT "interface&class=%s&subclass=0x%02x"
155#define IFACE_SUBCLASS_ARGS classname, desc_interface->interface_subclass
156
157#define IFACE_CLASS_FMT "interface&class=%s"
158#define IFACE_CLASS_ARGS classname
159
160#define VENDOR_RELEASE_FMT "vendor=0x%04x&product=0x%04x&release=" BCD_FMT
161#define VENDOR_RELEASE_ARGS desc_device->vendor_id, desc_device->product_id, \
162 BCD_ARGS(desc_device->device_version)
163
164#define VENDOR_PRODUCT_FMT "vendor=0x%04x&product=0x%04x"
165#define VENDOR_PRODUCT_ARGS desc_device->vendor_id, desc_device->product_id
166
167#define VENDOR_ONLY_FMT "vendor=0x%04x"
168#define VENDOR_ONLY_ARGS desc_device->vendor_id
169
170 /*
171 * If the vendor is specified, create match ids with vendor with
172 * higher score.
173 * Then the same ones without the vendor part.
174 */
175 if ((desc_device != NULL) && (desc_device->vendor_id != 0)) {
176 /* First, interface matches with device release number. */
177 ADD_MATCHID_OR_RETURN(matches, 250,
178 "usb&" VENDOR_RELEASE_FMT "&" IFACE_PROTOCOL_FMT,
179 VENDOR_RELEASE_ARGS, IFACE_PROTOCOL_ARGS);
180 ADD_MATCHID_OR_RETURN(matches, 240,
181 "usb&" VENDOR_RELEASE_FMT "&" IFACE_SUBCLASS_FMT,
182 VENDOR_RELEASE_ARGS, IFACE_SUBCLASS_ARGS);
183 ADD_MATCHID_OR_RETURN(matches, 230,
184 "usb&" VENDOR_RELEASE_FMT "&" IFACE_CLASS_FMT,
185 VENDOR_RELEASE_ARGS, IFACE_CLASS_ARGS);
186
187 /* Next, interface matches without release number. */
188 ADD_MATCHID_OR_RETURN(matches, 220,
189 "usb&" VENDOR_PRODUCT_FMT "&" IFACE_PROTOCOL_FMT,
190 VENDOR_PRODUCT_ARGS, IFACE_PROTOCOL_ARGS);
191 ADD_MATCHID_OR_RETURN(matches, 210,
192 "usb&" VENDOR_PRODUCT_FMT "&" IFACE_SUBCLASS_FMT,
193 VENDOR_PRODUCT_ARGS, IFACE_SUBCLASS_ARGS);
194 ADD_MATCHID_OR_RETURN(matches, 200,
195 "usb&" VENDOR_PRODUCT_FMT "&" IFACE_CLASS_FMT,
196 VENDOR_PRODUCT_ARGS, IFACE_CLASS_ARGS);
197
198 /* Finally, interface matches with only vendor. */
199 ADD_MATCHID_OR_RETURN(matches, 190,
200 "usb&" VENDOR_ONLY_FMT "&" IFACE_PROTOCOL_FMT,
201 VENDOR_ONLY_ARGS, IFACE_PROTOCOL_ARGS);
202 ADD_MATCHID_OR_RETURN(matches, 180,
203 "usb&" VENDOR_ONLY_FMT "&" IFACE_SUBCLASS_FMT,
204 VENDOR_ONLY_ARGS, IFACE_SUBCLASS_ARGS);
205 ADD_MATCHID_OR_RETURN(matches, 170,
206 "usb&" VENDOR_ONLY_FMT "&" IFACE_CLASS_FMT,
207 VENDOR_ONLY_ARGS, IFACE_CLASS_ARGS);
208 }
209
210 /* Now, the same but without any vendor specification. */
211 ADD_MATCHID_OR_RETURN(matches, 160,
212 "usb&" IFACE_PROTOCOL_FMT,
213 IFACE_PROTOCOL_ARGS);
214 ADD_MATCHID_OR_RETURN(matches, 150,
215 "usb&" IFACE_SUBCLASS_FMT,
216 IFACE_SUBCLASS_ARGS);
217 ADD_MATCHID_OR_RETURN(matches, 140,
218 "usb&" IFACE_CLASS_FMT,
219 IFACE_CLASS_ARGS);
220
221#undef IFACE_PROTOCOL_FMT
222#undef IFACE_PROTOCOL_ARGS
223#undef IFACE_SUBCLASS_FMT
224#undef IFACE_SUBCLASS_ARGS
225#undef IFACE_CLASS_FMT
226#undef IFACE_CLASS_ARGS
227#undef VENDOR_RELEASE_FMT
228#undef VENDOR_RELEASE_ARGS
229#undef VENDOR_PRODUCT_FMT
230#undef VENDOR_PRODUCT_ARGS
231#undef VENDOR_ONLY_FMT
232#undef VENDOR_ONLY_ARGS
233
234 return EOK;
235}
236
237/** Create DDF match ids from USB device descriptor.
238 *
239 * @param matches List of match ids to extend.
240 * @param device_descriptor Device descriptor returned by given device.
241 * @return Error code.
242 */
243int usb_drv_create_match_ids_from_device_descriptor(
244 match_id_list_t *matches,
245 const usb_standard_device_descriptor_t *device_descriptor)
246{
247 /*
248 * Unless the vendor id is 0, the pair idVendor-idProduct
249 * quite uniquely describes the device.
250 */
251 if (device_descriptor->vendor_id != 0) {
252 /* First, with release number. */
253 ADD_MATCHID_OR_RETURN(matches, 100,
254 "usb&vendor=0x%04x&product=0x%04x&release=" BCD_FMT,
255 (int) device_descriptor->vendor_id,
256 (int) device_descriptor->product_id,
257 BCD_ARGS(device_descriptor->device_version));
258
259 /* Next, without release number. */
260 ADD_MATCHID_OR_RETURN(matches, 90,
261 "usb&vendor=0x%04x&product=0x%04x",
262 (int) device_descriptor->vendor_id,
263 (int) device_descriptor->product_id);
264 }
265
266 /*
267 * If the device class points to interface we skip adding
268 * class directly but we add a multi interface device.
269 */
270 if (device_descriptor->device_class != USB_CLASS_USE_INTERFACE) {
271 ADD_MATCHID_OR_RETURN(matches, 50, "usb&class=%s",
272 usb_str_class(device_descriptor->device_class));
273 } else {
274 ADD_MATCHID_OR_RETURN(matches, 50, "usb&mid");
275 }
276
277 return EOK;
278}
279
280/** Create DDF match ids from USB configuration descriptor.
281 * The configuration descriptor is expected to be in the complete form,
282 * i.e. including interface, endpoint etc. descriptors.
283 *
284 * @param matches List of match ids to extend.
285 * @param config_descriptor Configuration descriptor returned by given device.
286 * @param total_size Size of the @p config_descriptor.
287 * @return Error code.
288 */
289int usb_drv_create_match_ids_from_configuration_descriptor(
290 match_id_list_t *matches,
291 const void *config_descriptor, size_t total_size)
292{
293 /*
294 * Iterate through config descriptor to find the interface
295 * descriptors.
296 */
297 size_t position = sizeof(usb_standard_configuration_descriptor_t);
298 while (position + 1 < total_size) {
299 uint8_t *current_descriptor
300 = ((uint8_t *) config_descriptor) + position;
301 uint8_t cur_descr_len = current_descriptor[0];
302 uint8_t cur_descr_type = current_descriptor[1];
303
304 if (cur_descr_len == 0) {
305 return ENOENT;
306 }
307
308 position += cur_descr_len;
309
310 if (cur_descr_type != USB_DESCTYPE_INTERFACE) {
311 continue;
312 }
313
314 /*
315 * Finally, we found an interface descriptor.
316 */
317 usb_standard_interface_descriptor_t *interface
318 = (usb_standard_interface_descriptor_t *)
319 current_descriptor;
320
321 ADD_MATCHID_OR_RETURN(matches, 50,
322 "usb&interface&class=%s",
323 usb_str_class(interface->interface_class));
324 }
325
326 return EOK;
327}
328
329/** Add match ids based on configuration descriptor.
330 *
331 * @param pipe Control pipe to the device.
332 * @param matches Match ids list to add matches to.
333 * @param config_count Number of configurations the device has.
334 * @return Error code.
335 */
336static int usb_add_config_descriptor_match_ids(usb_endpoint_pipe_t *pipe,
337 match_id_list_t *matches, int config_count)
338{
339 int final_rc = EOK;
340
341 int config_index;
342 for (config_index = 0; config_index < config_count; config_index++) {
343 int rc;
344 usb_standard_configuration_descriptor_t config_descriptor;
345 rc = usb_request_get_bare_configuration_descriptor(pipe,
346 config_index, &config_descriptor);
347 if (rc != EOK) {
348 final_rc = rc;
349 continue;
350 }
351
352 size_t full_config_descriptor_size;
353 void *full_config_descriptor
354 = malloc(config_descriptor.total_length);
355 rc = usb_request_get_full_configuration_descriptor(pipe,
356 config_index,
357 full_config_descriptor, config_descriptor.total_length,
358 &full_config_descriptor_size);
359 if (rc != EOK) {
360 final_rc = rc;
361 continue;
362 }
363 if (full_config_descriptor_size
364 != config_descriptor.total_length) {
365 final_rc = ERANGE;
366 continue;
367 }
368
369 rc = usb_drv_create_match_ids_from_configuration_descriptor(
370 matches,
371 full_config_descriptor, full_config_descriptor_size);
372 if (rc != EOK) {
373 final_rc = rc;
374 continue;
375 }
376
377 }
378
379 return final_rc;
380}
381
382/** Create match ids describing attached device.
383 *
384 * @warning The list of match ids @p matches may change even when
385 * function exits with error.
386 *
387 * @param ctrl_pipe Control pipe to given device (session must be already
388 * started).
389 * @param matches Initialized list of match ids.
390 * @return Error code.
391 */
392int usb_device_create_match_ids(usb_endpoint_pipe_t *ctrl_pipe,
393 match_id_list_t *matches)
394{
395 int rc;
396 /*
397 * Retrieve device descriptor and add matches from it.
398 */
399 usb_standard_device_descriptor_t device_descriptor;
400
401 rc = usb_request_get_device_descriptor(ctrl_pipe, &device_descriptor);
402 if (rc != EOK) {
403 return rc;
404 }
405
406 rc = usb_drv_create_match_ids_from_device_descriptor(matches,
407 &device_descriptor);
408 if (rc != EOK) {
409 return rc;
410 }
411
412 /*
413 * Go through all configurations and add matches
414 * based on interface class.
415 */
416 rc = usb_add_config_descriptor_match_ids(ctrl_pipe, matches,
417 device_descriptor.configuration_count);
418 if (rc != EOK) {
419 return rc;
420 }
421
422 /*
423 * As a fallback, provide the simplest match id possible.
424 */
425 ADD_MATCHID_OR_RETURN(matches, 1, "usb&fallback");
426
427 return EOK;
428}
429
430/** Probe for device kind and register it in devman.
431 *
432 * @param[in] address Address of the (unknown) attached device.
433 * @param[in] hc_handle Handle of the host controller.
434 * @param[in] parent Parent device.
435 * @param[out] child_handle Handle of the child device.
436 * @return Error code.
437 */
438int usb_device_register_child_in_devman(usb_address_t address,
439 devman_handle_t hc_handle,
440 device_t *parent, devman_handle_t *child_handle)
441{
442 size_t this_device_name_index;
443
444 fibril_mutex_lock(&device_name_index_mutex);
445 this_device_name_index = device_name_index;
446 device_name_index++;
447 fibril_mutex_unlock(&device_name_index_mutex);
448
449 device_t *child = NULL;
450 char *child_name = NULL;
451 int rc;
452 usb_device_connection_t dev_connection;
453 usb_endpoint_pipe_t ctrl_pipe;
454
455 rc = usb_device_connection_initialize(&dev_connection, hc_handle, address);
456 if (rc != EOK) {
457 goto failure;
458 }
459
460 rc = usb_endpoint_pipe_initialize_default_control(&ctrl_pipe,
461 &dev_connection);
462 if (rc != EOK) {
463 goto failure;
464 }
465
466 child = create_device();
467 if (child == NULL) {
468 rc = ENOMEM;
469 goto failure;
470 }
471
472 /*
473 * TODO: Once the device driver framework support persistent
474 * naming etc., something more descriptive could be created.
475 */
476 rc = asprintf(&child_name, "usbdev%02zu", this_device_name_index);
477 if (rc < 0) {
478 goto failure;
479 }
480 child->parent = parent;
481 child->name = child_name;
482 child->ops = &child_ops;
483
484 rc = usb_endpoint_pipe_start_session(&ctrl_pipe);
485 if (rc != EOK) {
486 goto failure;
487 }
488
489 rc = usb_device_create_match_ids(&ctrl_pipe, &child->match_ids);
490 if (rc != EOK) {
491 goto failure;
492 }
493
494 rc = usb_endpoint_pipe_end_session(&ctrl_pipe);
495 if (rc != EOK) {
496 goto failure;
497 }
498
499 rc = child_device_register(child, parent);
500 if (rc != EOK) {
501 goto failure;
502 }
503
504 if (child_handle != NULL) {
505 *child_handle = child->handle;
506 }
507
508 return EOK;
509
510failure:
511 if (child != NULL) {
512 child->name = NULL;
513 /* This takes care of match_id deallocation as well. */
514 delete_device(child);
515 }
516 if (child_name != NULL) {
517 free(child_name);
518 }
519
520 return rc;
521}
522
523
524/**
525 * @}
526 */
Note: See TracBrowser for help on using the repository browser.