source: mainline/uspace/lib/usb/src/devdrv.c@ 85c47729

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

Add base support for alternate interfaces

  • Property mode set to 100644
File size: 13.6 KB
Line 
1/*
2 * Copyright (c) 2011 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 * USB device driver framework.
34 */
35#include <usb/devdrv.h>
36#include <usb/request.h>
37#include <usb/debug.h>
38#include <usb/dp.h>
39#include <errno.h>
40#include <str_error.h>
41#include <assert.h>
42
43static int generic_add_device(ddf_dev_t *);
44
45static driver_ops_t generic_driver_ops = {
46 .add_device = generic_add_device
47};
48static driver_t generic_driver = {
49 .driver_ops = &generic_driver_ops
50};
51
52static usb_driver_t *driver = NULL;
53
54
55/** Main routine of USB device driver.
56 *
57 * Under normal conditions, this function never returns.
58 *
59 * @param drv USB device driver structure.
60 * @return Task exit status.
61 */
62int usb_driver_main(usb_driver_t *drv)
63{
64 assert(drv != NULL);
65
66 /* Prepare the generic driver. */
67 generic_driver.name = drv->name;
68
69 driver = drv;
70
71 return ddf_driver_main(&generic_driver);
72}
73
74/** Log out of memory error on given device.
75 *
76 * @param dev Device causing the trouble.
77 */
78static void usb_log_oom(ddf_dev_t *dev)
79{
80 usb_log_error("Out of memory when adding device `%s'.\n",
81 dev->name);
82}
83
84/** Count number of pipes the driver expects.
85 *
86 * @param drv USB driver.
87 * @return Number of pipes (excluding default control pipe).
88 */
89static size_t count_other_pipes(usb_endpoint_description_t **endpoints)
90{
91 size_t count = 0;
92 if (endpoints == NULL) {
93 return 0;
94 }
95
96 while (endpoints[count] != NULL) {
97 count++;
98 }
99
100 return count;
101}
102
103/** Initialize endpoint pipes, excluding default control one.
104 *
105 * @param drv The device driver.
106 * @param dev Device to be initialized.
107 * @return Error code.
108 */
109static int initialize_other_pipes(usb_endpoint_description_t **endpoints,
110 usb_device_t *dev)
111{
112 int rc;
113
114 size_t pipe_count = count_other_pipes(endpoints);
115 if (pipe_count == 0) {
116 return EOK;
117 }
118
119 dev->pipes = malloc(sizeof(usb_endpoint_mapping_t) * pipe_count);
120 if (dev->pipes == NULL) {
121 usb_log_oom(dev->ddf_dev);
122 return ENOMEM;
123 }
124
125 size_t i;
126
127 /* Initialize to NULL first for rollback purposes. */
128 for (i = 0; i < pipe_count; i++) {
129 dev->pipes[i].pipe = NULL;
130 }
131
132 for (i = 0; i < pipe_count; i++) {
133 dev->pipes[i].pipe = malloc(sizeof(usb_pipe_t));
134 if (dev->pipes[i].pipe == NULL) {
135 usb_log_oom(dev->ddf_dev);
136 rc = ENOMEM;
137 goto rollback;
138 }
139
140 dev->pipes[i].description = endpoints[i];
141 dev->pipes[i].interface_no = dev->interface_no;
142 dev->pipes[i].interface_setting = 0;
143 }
144
145 rc = usb_pipe_initialize_from_configuration(dev->pipes, pipe_count,
146 dev->descriptors.configuration, dev->descriptors.configuration_size,
147 &dev->wire);
148 if (rc != EOK) {
149 usb_log_error("Failed initializing USB endpoints: %s.\n",
150 str_error(rc));
151 goto rollback;
152 }
153
154 /* Register the endpoints. */
155 usb_hc_connection_t hc_conn;
156 rc = usb_hc_connection_initialize_from_device(&hc_conn, dev->ddf_dev);
157 if (rc != EOK) {
158 usb_log_error(
159 "Failed initializing connection to host controller: %s.\n",
160 str_error(rc));
161 goto rollback;
162 }
163 rc = usb_hc_connection_open(&hc_conn);
164 if (rc != EOK) {
165 usb_log_error("Failed to connect to host controller: %s.\n",
166 str_error(rc));
167 goto rollback;
168 }
169 for (i = 0; i < pipe_count; i++) {
170 if (dev->pipes[i].present) {
171 rc = usb_pipe_register(dev->pipes[i].pipe,
172 dev->pipes[i].descriptor->poll_interval,
173 &hc_conn);
174 /* Ignore error when operation not supported by HC. */
175 if ((rc != EOK) && (rc != ENOTSUP)) {
176 /* FIXME: what shall we do? */
177 dev->pipes[i].present = false;
178 free(dev->pipes[i].pipe);
179 dev->pipes[i].pipe = NULL;
180 }
181 }
182 }
183 /* Ignoring errors here. */
184 usb_hc_connection_close(&hc_conn);
185
186 dev->pipes_count = pipe_count;
187
188 return EOK;
189
190rollback:
191 for (i = 0; i < pipe_count; i++) {
192 if (dev->pipes[i].pipe != NULL) {
193 free(dev->pipes[i].pipe);
194 }
195 }
196 free(dev->pipes);
197
198 return rc;
199}
200
201/** Initialize all endpoint pipes.
202 *
203 * @param drv The driver.
204 * @param dev The device to be initialized.
205 * @return Error code.
206 */
207static int initialize_pipes(usb_device_t *dev)
208{
209 int rc;
210
211 rc = usb_device_connection_initialize_from_device(&dev->wire,
212 dev->ddf_dev);
213 if (rc != EOK) {
214 usb_log_error(
215 "Failed initializing connection on device `%s'. %s.\n",
216 dev->ddf_dev->name, str_error(rc));
217 return rc;
218 }
219
220 rc = usb_pipe_initialize_default_control(&dev->ctrl_pipe,
221 &dev->wire);
222 if (rc != EOK) {
223 usb_log_error("Failed to initialize default control pipe " \
224 "on device `%s': %s.\n",
225 dev->ddf_dev->name, str_error(rc));
226 return rc;
227 }
228
229 rc = usb_pipe_probe_default_control(&dev->ctrl_pipe);
230 if (rc != EOK) {
231 usb_log_error(
232 "Probing default control pipe on device `%s' failed: %s.\n",
233 dev->ddf_dev->name, str_error(rc));
234 return rc;
235 }
236
237 /* Get our interface. */
238 dev->interface_no = usb_device_get_assigned_interface(dev->ddf_dev);
239
240 /*
241 * For further actions, we need open session on default control pipe.
242 */
243 rc = usb_pipe_start_session(&dev->ctrl_pipe);
244 if (rc != EOK) {
245 usb_log_error("Failed to start an IPC session: %s.\n",
246 str_error(rc));
247 return rc;
248 }
249
250 /* Get the device descriptor. */
251 rc = usb_request_get_device_descriptor(&dev->ctrl_pipe,
252 &dev->descriptors.device);
253 if (rc != EOK) {
254 usb_log_error("Failed to retrieve device descriptor: %s.\n",
255 str_error(rc));
256 return rc;
257 }
258
259 /* Get the full configuration descriptor. */
260 rc = usb_request_get_full_configuration_descriptor_alloc(
261 &dev->ctrl_pipe, 0, (void **) &dev->descriptors.configuration,
262 &dev->descriptors.configuration_size);
263 if (rc != EOK) {
264 usb_log_error("Failed retrieving configuration descriptor: %s. %s\n",
265 dev->ddf_dev->name, str_error(rc));
266 return rc;
267 }
268
269 if (driver->endpoints != NULL) {
270 rc = initialize_other_pipes(driver->endpoints, dev);
271 }
272
273 /* No checking here. */
274 usb_pipe_end_session(&dev->ctrl_pipe);
275
276 /* Rollback actions. */
277 if (rc != EOK) {
278 if (dev->descriptors.configuration != NULL) {
279 free(dev->descriptors.configuration);
280 }
281 }
282
283 return rc;
284}
285
286/** Count number of alternate settings of a interface.
287 *
288 * @param config_descr Full configuration descriptor.
289 * @param config_descr_size Size of @p config_descr in bytes.
290 * @param interface_no Interface number.
291 * @return Number of alternate interfaces for @p interface_no interface.
292 */
293static size_t count_alternate_interfaces(uint8_t *config_descr,
294 size_t config_descr_size, int interface_no)
295{
296 assert(config_descr != NULL);
297 usb_dp_parser_t dp_parser = {
298 .nesting = usb_dp_standard_descriptor_nesting
299 };
300 usb_dp_parser_data_t dp_data = {
301 .data = config_descr,
302 .size = config_descr_size,
303 .arg = NULL
304 };
305
306 size_t alternate_count = 0;
307
308 uint8_t *iface_ptr = usb_dp_get_nested_descriptor(&dp_parser,
309 &dp_data, config_descr);
310 while (iface_ptr != NULL) {
311 usb_standard_interface_descriptor_t *iface
312 = (usb_standard_interface_descriptor_t *) iface_ptr;
313 if (iface->descriptor_type == USB_DESCTYPE_INTERFACE) {
314 if (iface->interface_number == interface_no) {
315 alternate_count++;
316 }
317 }
318 iface_ptr = usb_dp_get_sibling_descriptor(&dp_parser, &dp_data,
319 config_descr, iface_ptr);
320 }
321
322 return alternate_count;
323}
324
325/** Initialize structures related to alternate interfaces.
326 *
327 * @param dev Device where alternate settings shall be initialized.
328 * @return Error code.
329 */
330static int initialize_alternate_interfaces(usb_device_t *dev)
331{
332 if (dev->interface_no < 0) {
333 dev->alternate_interfaces = NULL;
334 return EOK;
335 }
336
337 usb_alternate_interfaces_t *alternates
338 = malloc(sizeof(usb_alternate_interfaces_t));
339
340 if (alternates == NULL) {
341 return ENOMEM;
342 }
343
344 alternates->alternative_count
345 = count_alternate_interfaces(dev->descriptors.configuration,
346 dev->descriptors.configuration_size, dev->interface_no);
347
348 if (alternates->alternative_count == 0) {
349 free(alternates);
350 return ENOENT;
351 }
352
353 alternates->alternatives = malloc(alternates->alternative_count
354 * sizeof(usb_alternate_interface_descriptors_t));
355 if (alternates->alternatives == NULL) {
356 free(alternates);
357 return ENOMEM;
358 }
359
360 alternates->current = 0;
361
362 usb_dp_parser_t dp_parser = {
363 .nesting = usb_dp_standard_descriptor_nesting
364 };
365 usb_dp_parser_data_t dp_data = {
366 .data = dev->descriptors.configuration,
367 .size = dev->descriptors.configuration_size,
368 .arg = NULL
369 };
370
371 usb_alternate_interface_descriptors_t *cur_alt_iface
372 = &alternates->alternatives[0];
373
374 uint8_t *iface_ptr = usb_dp_get_nested_descriptor(&dp_parser,
375 &dp_data, dp_data.data);
376 while (iface_ptr != NULL) {
377 usb_standard_interface_descriptor_t *iface
378 = (usb_standard_interface_descriptor_t *) iface_ptr;
379 if ((iface->descriptor_type != USB_DESCTYPE_INTERFACE)
380 || (iface->interface_number != dev->interface_no)) {
381 iface_ptr = usb_dp_get_sibling_descriptor(&dp_parser,
382 &dp_data,
383 dp_data.data, iface_ptr);
384 continue;
385 }
386
387 cur_alt_iface->interface = iface;
388 cur_alt_iface->nested_descriptors = iface_ptr + sizeof(*iface);
389
390 /* Find next interface to count size of nested descriptors. */
391 iface_ptr = usb_dp_get_sibling_descriptor(&dp_parser, &dp_data,
392 dp_data.data, iface_ptr);
393 if (iface_ptr == NULL) {
394 uint8_t *next = dp_data.data + dp_data.size;
395 cur_alt_iface->nested_descriptors_size
396 = next - cur_alt_iface->nested_descriptors;
397 } else {
398 cur_alt_iface->nested_descriptors_size
399 = iface_ptr - cur_alt_iface->nested_descriptors;
400 }
401
402 cur_alt_iface++;
403 }
404
405 dev->alternate_interfaces = alternates;
406
407 return EOK;
408}
409
410/** Callback when new device is supposed to be controlled by this driver.
411 *
412 * This callback is a wrapper for USB specific version of @c add_device.
413 *
414 * @param gen_dev Device structure as prepared by DDF.
415 * @return Error code.
416 */
417int generic_add_device(ddf_dev_t *gen_dev)
418{
419 assert(driver);
420 assert(driver->ops);
421 assert(driver->ops->add_device);
422
423 int rc;
424
425 usb_device_t *dev = malloc(sizeof(usb_device_t));
426 if (dev == NULL) {
427 usb_log_error("Out of memory when adding device `%s'.\n",
428 gen_dev->name);
429 return ENOMEM;
430 }
431
432
433 dev->ddf_dev = gen_dev;
434 dev->ddf_dev->driver_data = dev;
435 dev->driver_data = NULL;
436 dev->descriptors.configuration = NULL;
437
438 dev->pipes_count = 0;
439 dev->pipes = NULL;
440
441 rc = initialize_pipes(dev);
442 if (rc != EOK) {
443 free(dev);
444 return rc;
445 }
446
447 (void) initialize_alternate_interfaces(dev);
448
449 return driver->ops->add_device(dev);
450}
451
452/** Destroy existing pipes of a USB device.
453 *
454 * @param dev Device where to destroy the pipes.
455 * @return Error code.
456 */
457static int destroy_current_pipes(usb_device_t *dev)
458{
459 size_t i;
460 int rc;
461
462 /* TODO: this shall be done under some device mutex. */
463
464 /* First check that no session is opened. */
465 for (i = 0; i < dev->pipes_count; i++) {
466 if (usb_pipe_is_session_started(dev->pipes[i].pipe)) {
467 return EBUSY;
468 }
469 }
470
471 /* Prepare connection to HC. */
472 usb_hc_connection_t hc_conn;
473 rc = usb_hc_connection_initialize_from_device(&hc_conn, dev->ddf_dev);
474 if (rc != EOK) {
475 return rc;
476 }
477 rc = usb_hc_connection_open(&hc_conn);
478 if (rc != EOK) {
479 return rc;
480 }
481
482 /* Destroy the pipes. */
483 for (i = 0; i < dev->pipes_count; i++) {
484 usb_pipe_unregister(dev->pipes[i].pipe, &hc_conn);
485 free(dev->pipes[i].pipe);
486 }
487
488 usb_hc_connection_close(&hc_conn);
489
490 free(dev->pipes);
491 dev->pipes = NULL;
492 dev->pipes_count = 0;
493
494 return EOK;
495}
496
497/** Change interface setting of a device.
498 * This function selects new alternate setting of an interface by issuing
499 * proper USB command to the device and also creates new USB pipes
500 * under @c dev->pipes.
501 *
502 * @warning This function is intended for drivers working at interface level.
503 * For drivers controlling the whole device, you need to change interface
504 * manually using usb_request_set_interface() and creating new pipes
505 * with usb_pipe_initialize_from_configuration().
506 *
507 * @param dev USB device.
508 * @param alternate_setting Alternate setting to choose.
509 * @param endpoints New endpoint descriptions.
510 * @return Error code.
511 */
512int usb_device_select_interface(usb_device_t *dev, uint8_t alternate_setting,
513 usb_endpoint_description_t **endpoints)
514{
515 if (dev->interface_no < 0) {
516 return EINVAL;
517 }
518
519 int rc;
520
521 /* TODO: more transactional behavior. */
522
523 /* Destroy existing pipes. */
524 rc = destroy_current_pipes(dev);
525 if (rc != EOK) {
526 return rc;
527 }
528
529 /* Change the interface itself. */
530 rc = usb_request_set_interface(&dev->ctrl_pipe, dev->interface_no,
531 alternate_setting);
532 if (rc != EOK) {
533 return rc;
534 }
535
536 /* Create new pipes. */
537 rc = initialize_other_pipes(endpoints, dev);
538
539 return rc;
540}
541
542/**
543 * @}
544 */
Note: See TracBrowser for help on using the repository browser.