source: mainline/uspace/lib/usbdev/src/recognise.c@ 07a7a97d

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 07a7a97d was 59c163c, checked in by Jan Vesely <jano.vesely@…>, 14 years ago

libusbdev: Automatically create attached device info for new devices.

  • Property mode set to 100644
File size: 13.0 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 libusbdev
30 * @{
31 */
32/** @file
33 * Functions for recognition of attached devices.
34 */
35#include <sys/types.h>
36#include <fibril_synch.h>
37#include <usb/debug.h>
38#include <usb/dev/hub.h>
39#include <usb/dev/pipes.h>
40#include <usb/dev/recognise.h>
41#include <usb/ddfiface.h>
42#include <usb/dev/request.h>
43#include <usb/classes/classes.h>
44#include <stdio.h>
45#include <errno.h>
46#include <assert.h>
47
48/** Index to append after device name for uniqueness. */
49static size_t device_name_index = 0;
50/** Mutex guard for device_name_index. */
51static FIBRIL_MUTEX_INITIALIZE(device_name_index_mutex);
52
53/** DDF operations of child devices. */
54static ddf_dev_ops_t child_ops = {
55 .interfaces[USB_DEV_IFACE] = &usb_iface_hub_child_impl
56};
57
58/** Get integer part from BCD coded number. */
59#define BCD_INT(a) (((unsigned int)(a)) / 256)
60/** Get fraction part from BCD coded number (as an integer, no less). */
61#define BCD_FRAC(a) (((unsigned int)(a)) % 256)
62
63/** Format for BCD coded number to be used in printf. */
64#define BCD_FMT "%x.%x"
65/** Arguments to printf for BCD coded number. */
66#define BCD_ARGS(a) BCD_INT((a)), BCD_FRAC((a))
67
68/* FIXME: make this dynamic */
69#define MATCH_STRING_MAX 256
70
71/** Add formatted match id.
72 *
73 * @param matches List of match ids where to add to.
74 * @param score Score of the match.
75 * @param format Printf-like format
76 * @return Error code.
77 */
78static int usb_add_match_id(match_id_list_t *matches, int score,
79 const char *format, ...)
80{
81 char *match_str = NULL;
82 match_id_t *match_id = NULL;
83 int rc;
84
85 match_str = malloc(MATCH_STRING_MAX + 1);
86 if (match_str == NULL) {
87 rc = ENOMEM;
88 goto failure;
89 }
90
91 /*
92 * FIXME: replace with dynamic allocation of exact size
93 */
94 va_list args;
95 va_start(args, format );
96 vsnprintf(match_str, MATCH_STRING_MAX, format, args);
97 match_str[MATCH_STRING_MAX] = 0;
98 va_end(args);
99
100 match_id = create_match_id();
101 if (match_id == NULL) {
102 rc = ENOMEM;
103 goto failure;
104 }
105
106 match_id->id = match_str;
107 match_id->score = score;
108 add_match_id(matches, match_id);
109
110 return EOK;
111
112failure:
113 if (match_str != NULL) {
114 free(match_str);
115 }
116 if (match_id != NULL) {
117 match_id->id = NULL;
118 delete_match_id(match_id);
119 }
120
121 return rc;
122}
123
124/** Add match id to list or return with error code.
125 *
126 * @param match_ids List of match ids.
127 * @param score Match id score.
128 * @param format Format of the matching string
129 * @param ... Arguments for the format.
130 */
131#define ADD_MATCHID_OR_RETURN(match_ids, score, format, ...) \
132 do { \
133 int __rc = usb_add_match_id((match_ids), (score), \
134 format, ##__VA_ARGS__); \
135 if (__rc != EOK) { \
136 return __rc; \
137 } \
138 } while (0)
139
140/** Create device match ids based on its interface.
141 *
142 * @param[in] desc_device Device descriptor.
143 * @param[in] desc_interface Interface descriptor.
144 * @param[out] matches Initialized list of match ids.
145 * @return Error code (the two mentioned are not the only ones).
146 * @retval EINVAL Invalid input parameters (expects non NULL pointers).
147 * @retval ENOENT Device class is not "use interface".
148 */
149int usb_device_create_match_ids_from_interface(
150 const usb_standard_device_descriptor_t *desc_device,
151 const usb_standard_interface_descriptor_t *desc_interface,
152 match_id_list_t *matches)
153{
154 if (desc_interface == NULL) {
155 return EINVAL;
156 }
157 if (matches == NULL) {
158 return EINVAL;
159 }
160
161 if (desc_interface->interface_class == USB_CLASS_USE_INTERFACE) {
162 return ENOENT;
163 }
164
165 const char *classname = usb_str_class(desc_interface->interface_class);
166 assert(classname != NULL);
167
168#define IFACE_PROTOCOL_FMT "interface&class=%s&subclass=0x%02x&protocol=0x%02x"
169#define IFACE_PROTOCOL_ARGS classname, desc_interface->interface_subclass, \
170 desc_interface->interface_protocol
171
172#define IFACE_SUBCLASS_FMT "interface&class=%s&subclass=0x%02x"
173#define IFACE_SUBCLASS_ARGS classname, desc_interface->interface_subclass
174
175#define IFACE_CLASS_FMT "interface&class=%s"
176#define IFACE_CLASS_ARGS classname
177
178#define VENDOR_RELEASE_FMT "vendor=0x%04x&product=0x%04x&release=" BCD_FMT
179#define VENDOR_RELEASE_ARGS desc_device->vendor_id, desc_device->product_id, \
180 BCD_ARGS(desc_device->device_version)
181
182#define VENDOR_PRODUCT_FMT "vendor=0x%04x&product=0x%04x"
183#define VENDOR_PRODUCT_ARGS desc_device->vendor_id, desc_device->product_id
184
185#define VENDOR_ONLY_FMT "vendor=0x%04x"
186#define VENDOR_ONLY_ARGS desc_device->vendor_id
187
188 /*
189 * If the vendor is specified, create match ids with vendor with
190 * higher score.
191 * Then the same ones without the vendor part.
192 */
193 if ((desc_device != NULL) && (desc_device->vendor_id != 0)) {
194 /* First, interface matches with device release number. */
195 ADD_MATCHID_OR_RETURN(matches, 250,
196 "usb&" VENDOR_RELEASE_FMT "&" IFACE_PROTOCOL_FMT,
197 VENDOR_RELEASE_ARGS, IFACE_PROTOCOL_ARGS);
198 ADD_MATCHID_OR_RETURN(matches, 240,
199 "usb&" VENDOR_RELEASE_FMT "&" IFACE_SUBCLASS_FMT,
200 VENDOR_RELEASE_ARGS, IFACE_SUBCLASS_ARGS);
201 ADD_MATCHID_OR_RETURN(matches, 230,
202 "usb&" VENDOR_RELEASE_FMT "&" IFACE_CLASS_FMT,
203 VENDOR_RELEASE_ARGS, IFACE_CLASS_ARGS);
204
205 /* Next, interface matches without release number. */
206 ADD_MATCHID_OR_RETURN(matches, 220,
207 "usb&" VENDOR_PRODUCT_FMT "&" IFACE_PROTOCOL_FMT,
208 VENDOR_PRODUCT_ARGS, IFACE_PROTOCOL_ARGS);
209 ADD_MATCHID_OR_RETURN(matches, 210,
210 "usb&" VENDOR_PRODUCT_FMT "&" IFACE_SUBCLASS_FMT,
211 VENDOR_PRODUCT_ARGS, IFACE_SUBCLASS_ARGS);
212 ADD_MATCHID_OR_RETURN(matches, 200,
213 "usb&" VENDOR_PRODUCT_FMT "&" IFACE_CLASS_FMT,
214 VENDOR_PRODUCT_ARGS, IFACE_CLASS_ARGS);
215
216 /* Finally, interface matches with only vendor. */
217 ADD_MATCHID_OR_RETURN(matches, 190,
218 "usb&" VENDOR_ONLY_FMT "&" IFACE_PROTOCOL_FMT,
219 VENDOR_ONLY_ARGS, IFACE_PROTOCOL_ARGS);
220 ADD_MATCHID_OR_RETURN(matches, 180,
221 "usb&" VENDOR_ONLY_FMT "&" IFACE_SUBCLASS_FMT,
222 VENDOR_ONLY_ARGS, IFACE_SUBCLASS_ARGS);
223 ADD_MATCHID_OR_RETURN(matches, 170,
224 "usb&" VENDOR_ONLY_FMT "&" IFACE_CLASS_FMT,
225 VENDOR_ONLY_ARGS, IFACE_CLASS_ARGS);
226 }
227
228 /* Now, the same but without any vendor specification. */
229 ADD_MATCHID_OR_RETURN(matches, 160,
230 "usb&" IFACE_PROTOCOL_FMT,
231 IFACE_PROTOCOL_ARGS);
232 ADD_MATCHID_OR_RETURN(matches, 150,
233 "usb&" IFACE_SUBCLASS_FMT,
234 IFACE_SUBCLASS_ARGS);
235 ADD_MATCHID_OR_RETURN(matches, 140,
236 "usb&" IFACE_CLASS_FMT,
237 IFACE_CLASS_ARGS);
238
239#undef IFACE_PROTOCOL_FMT
240#undef IFACE_PROTOCOL_ARGS
241#undef IFACE_SUBCLASS_FMT
242#undef IFACE_SUBCLASS_ARGS
243#undef IFACE_CLASS_FMT
244#undef IFACE_CLASS_ARGS
245#undef VENDOR_RELEASE_FMT
246#undef VENDOR_RELEASE_ARGS
247#undef VENDOR_PRODUCT_FMT
248#undef VENDOR_PRODUCT_ARGS
249#undef VENDOR_ONLY_FMT
250#undef VENDOR_ONLY_ARGS
251
252 /* As a last resort, try fallback driver. */
253 ADD_MATCHID_OR_RETURN(matches, 10, "usb&interface&fallback");
254
255 return EOK;
256}
257
258/** Create DDF match ids from USB device descriptor.
259 *
260 * @param matches List of match ids to extend.
261 * @param device_descriptor Device descriptor returned by given device.
262 * @return Error code.
263 */
264int usb_device_create_match_ids_from_device_descriptor(
265 const usb_standard_device_descriptor_t *device_descriptor,
266 match_id_list_t *matches)
267{
268 /*
269 * Unless the vendor id is 0, the pair idVendor-idProduct
270 * quite uniquely describes the device.
271 */
272 if (device_descriptor->vendor_id != 0) {
273 /* First, with release number. */
274 ADD_MATCHID_OR_RETURN(matches, 100,
275 "usb&vendor=0x%04x&product=0x%04x&release=" BCD_FMT,
276 (int) device_descriptor->vendor_id,
277 (int) device_descriptor->product_id,
278 BCD_ARGS(device_descriptor->device_version));
279
280 /* Next, without release number. */
281 ADD_MATCHID_OR_RETURN(matches, 90,
282 "usb&vendor=0x%04x&product=0x%04x",
283 (int) device_descriptor->vendor_id,
284 (int) device_descriptor->product_id);
285 }
286
287 /*
288 * If the device class points to interface we skip adding
289 * class directly but we add a multi interface device.
290 */
291 if (device_descriptor->device_class != USB_CLASS_USE_INTERFACE) {
292 ADD_MATCHID_OR_RETURN(matches, 50, "usb&class=%s",
293 usb_str_class(device_descriptor->device_class));
294 } else {
295 ADD_MATCHID_OR_RETURN(matches, 50, "usb&mid");
296 }
297
298 /* As a last resort, try fallback driver. */
299 ADD_MATCHID_OR_RETURN(matches, 10, "usb&fallback");
300
301 return EOK;
302}
303
304
305/** Create match ids describing attached device.
306 *
307 * @warning The list of match ids @p matches may change even when
308 * function exits with error.
309 *
310 * @param ctrl_pipe Control pipe to given device (session must be already
311 * started).
312 * @param matches Initialized list of match ids.
313 * @return Error code.
314 */
315int usb_device_create_match_ids(usb_pipe_t *ctrl_pipe,
316 match_id_list_t *matches)
317{
318 int rc;
319 /*
320 * Retrieve device descriptor and add matches from it.
321 */
322 usb_standard_device_descriptor_t device_descriptor;
323
324 rc = usb_request_get_device_descriptor(ctrl_pipe, &device_descriptor);
325 if (rc != EOK) {
326 return rc;
327 }
328
329 rc = usb_device_create_match_ids_from_device_descriptor(
330 &device_descriptor, matches);
331 if (rc != EOK) {
332 return rc;
333 }
334
335 return EOK;
336}
337
338/** Probe for device kind and register it in devman.
339 *
340 * @param[in] address Address of the (unknown) attached device.
341 * @param[in] hc_handle Handle of the host controller.
342 * @param[in] parent Parent device.
343 * @param[in] dev_ops Child device ops. Default child_ops will be used if NULL.
344 * @param[in] dev_data Arbitrary pointer to be stored in the child
345 * as @c driver_data.
346 * @param[out] child_fun Storage where pointer to allocated child function
347 * will be written.
348 * @return Error code.
349 */
350int usb_device_register_child_in_devman(usb_address_t address,
351 devman_handle_t hc_handle, ddf_dev_t *parent,
352 ddf_dev_ops_t *dev_ops, void *dev_data, ddf_fun_t **child_fun)
353{
354 if (child_fun == NULL)
355 return EINVAL;
356
357 if (!dev_ops && dev_data) {
358 usb_log_warning("Using standard fun ops with arbitrary "
359 "driver data. This does not have to work.\n");
360 }
361
362 size_t this_device_name_index;
363
364 fibril_mutex_lock(&device_name_index_mutex);
365 this_device_name_index = device_name_index;
366 device_name_index++;
367 fibril_mutex_unlock(&device_name_index_mutex);
368
369 ddf_fun_t *child = NULL;
370 char *child_name = NULL;
371 int rc;
372 usb_device_connection_t dev_connection;
373 usb_pipe_t ctrl_pipe;
374
375 rc = usb_device_connection_initialize(
376 &dev_connection, hc_handle, address);
377 if (rc != EOK) {
378 goto failure;
379 }
380
381 rc = usb_pipe_initialize_default_control(&ctrl_pipe, &dev_connection);
382 if (rc != EOK) {
383 goto failure;
384 }
385 rc = usb_pipe_probe_default_control(&ctrl_pipe);
386 if (rc != EOK) {
387 goto failure;
388 }
389
390 /*
391 * TODO: Once the device driver framework support persistent
392 * naming etc., something more descriptive could be created.
393 */
394 rc = asprintf(&child_name, "usb%02zu_a%d",
395 this_device_name_index, address);
396 if (rc < 0) {
397 goto failure;
398 }
399
400 child = ddf_fun_create(parent, fun_inner, child_name);
401 free(child_name);
402 if (child == NULL) {
403 rc = ENOMEM;
404 goto failure;
405 }
406
407 if (dev_ops != NULL) {
408 child->ops = dev_ops;
409 } else {
410 child->ops = &child_ops;
411 }
412
413 child->driver_data = dev_data;
414 /* Store the attached device in fun driver data if there is no
415 * other data */
416 if (!dev_data) {
417 usb_hub_attached_device_t *new_device = ddf_fun_data_alloc(
418 child, sizeof(usb_hub_attached_device_t));
419 if (!new_device) {
420 rc = ENOMEM;
421 goto failure;
422 }
423 new_device->address = address;
424 new_device->fun = child;
425 }
426
427
428 rc = usb_device_create_match_ids(&ctrl_pipe, &child->match_ids);
429 if (rc != EOK) {
430 goto failure;
431 }
432
433 rc = ddf_fun_bind(child);
434 if (rc != EOK) {
435 goto failure;
436 }
437
438 *child_fun = child;
439 return EOK;
440
441failure:
442 if (child != NULL) {
443 /* We know nothing about the data if it came from outside. */
444 if (dev_data) {
445 child->driver_data = NULL;
446 }
447 /* This takes care of match_id deallocation as well. */
448 ddf_fun_destroy(child);
449 }
450
451 return rc;
452}
453
454
455/**
456 * @}
457 */
Note: See TracBrowser for help on using the repository browser.