source: mainline/uspace/lib/usbhost/src/ddf_helpers.c@ 3b60ea0

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 3b60ea0 was 5595841, checked in by Ondřej Hlavatý <aearsis@…>, 8 years ago

libdrv: usbhc_iface now shares memory instead of sending data

Two more things that come along:
1) The two callbacks (read, write) were joined. As they were joined in

the only implementation (ddf_helpers) anyway, and now the code is
almost the same, we took the opportunity and joined them. The bad
thing about it is that there are not enough IPC arguments to
transfer the direction, so we still have to use two "interface IPC
methods".

2) The copying is still done by the former methods (usbhc_read and

usbhc_write) to ensure the page alignment, so this method just
moves the burden of copying from kernel to the caller (which is why
this is actually a performance regression).

But, the two sides can now resolve their issues separately. The caller
can prepare the memory area in advance, and HC can use the memory
directly if it can.

  • Property mode set to 100644
File size: 12.7 KB
Line 
1/*
2 * Copyright (c) 2013 Jan Vesely
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 libusbhost
30 * @{
31 */
32/** @file
33 * Helpers to work with the DDF interface.
34 */
35
36#include <adt/list.h>
37#include <assert.h>
38#include <async.h>
39#include <ddf/driver.h>
40#include <ddf/interrupt.h>
41#include <device/hw_res_parsed.h>
42#include <errno.h>
43#include <str_error.h>
44#include <usb/classes/classes.h>
45#include <usb/debug.h>
46#include <usb/descriptor.h>
47#include <usb/usb.h>
48#include <usb_iface.h>
49#include <usbhc_iface.h>
50
51#include "bus.h"
52#include "endpoint.h"
53
54#include "ddf_helpers.h"
55
56/**
57 * DDF usbhc_iface callback. Passes the endpoint descriptors, fills the pipe
58 * descriptor according to the contents of the endpoint.
59 *
60 * @param[in] fun DDF function of the device in question.
61 * @param[out] pipe_desc The pipe descriptor to be filled.
62 * @param[in] endpoint_desc Endpoint descriptors from the device.
63 * @return Error code.
64 */
65static errno_t register_endpoint(ddf_fun_t *fun, usb_pipe_desc_t *pipe_desc,
66 const usb_endpoint_descriptors_t *ep_desc)
67{
68 assert(fun);
69 hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
70 device_t *dev = ddf_fun_data_get(fun);
71 assert(hcd);
72 assert(hcd->bus);
73 assert(dev);
74
75 endpoint_t *ep;
76 const int err = bus_endpoint_add(dev, ep_desc, &ep);
77 if (err)
78 return err;
79
80 if (pipe_desc) {
81 pipe_desc->endpoint_no = ep->endpoint;
82 pipe_desc->direction = ep->direction;
83 pipe_desc->transfer_type = ep->transfer_type;
84 pipe_desc->max_transfer_size = ep->max_transfer_size;
85 }
86 endpoint_del_ref(ep);
87
88 return EOK;
89}
90
91 /**
92 * DDF usbhc_iface callback. Unregister endpoint that makes the other end of
93 * the pipe described.
94 *
95 * @param fun DDF function of the device in question.
96 * @param pipe_desc Pipe description.
97 * @return Error code.
98 */
99static errno_t unregister_endpoint(ddf_fun_t *fun, const usb_pipe_desc_t *pipe_desc)
100{
101 assert(fun);
102 hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
103 device_t *dev = ddf_fun_data_get(fun);
104 assert(hcd);
105 assert(hcd->bus);
106 assert(dev);
107
108 endpoint_t *ep = bus_find_endpoint(dev, pipe_desc->endpoint_no, pipe_desc->direction);
109 if (!ep)
110 return ENOENT;
111
112 return bus_endpoint_remove(ep);
113}
114
115/**
116 * DDF usbhc_iface callback. Calls the respective bus operation directly.
117 *
118 * @param fun DDF function of the device (hub) requesting the address.
119 */
120static errno_t default_address_reservation(ddf_fun_t *fun, bool reserve)
121{
122 assert(fun);
123 hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
124 device_t *dev = ddf_fun_data_get(fun);
125 assert(hcd);
126 assert(hcd->bus);
127 assert(dev);
128
129 usb_log_debug("Device %d %s default address", dev->address, reserve ? "requested" : "releasing");
130 if (reserve) {
131 return bus_reserve_default_address(hcd->bus, dev);
132 } else {
133 bus_release_default_address(hcd->bus, dev);
134 return EOK;
135 }
136}
137
138/**
139 * DDF usbhc_iface callback. Calls the bus operation directly.
140 *
141 * @param fun DDF function of the device (hub) requesting the address.
142 * @param speed USB speed of the new device
143 */
144static errno_t device_enumerate(ddf_fun_t *fun, unsigned port, usb_speed_t speed)
145{
146 assert(fun);
147 ddf_dev_t *hc = ddf_fun_get_dev(fun);
148 assert(hc);
149 hc_device_t *hcd = dev_to_hcd(hc);
150 assert(hcd);
151 device_t *hub = ddf_fun_data_get(fun);
152 assert(hub);
153
154 errno_t err;
155
156 if (!usb_speed_is_valid(speed))
157 return EINVAL;
158
159 usb_log_debug("Hub %d reported a new %s speed device on port: %u",
160 hub->address, usb_str_speed(speed), port);
161
162 device_t *dev = hcd_ddf_fun_create(hcd, speed);
163 if (!dev) {
164 usb_log_error("Failed to create USB device function.");
165 return ENOMEM;
166 }
167
168 dev->hub = hub;
169 dev->tier = hub->tier + 1;
170 dev->port = port;
171 dev->speed = speed;
172
173 if ((err = bus_device_enumerate(dev))) {
174 usb_log_error("Failed to initialize USB dev memory structures.");
175 goto err_usb_dev;
176 }
177
178 /* If the driver didn't name the dev when enumerating,
179 * do it in some generic way.
180 */
181 if (!ddf_fun_get_name(dev->fun)) {
182 bus_device_set_default_name(dev);
183 }
184
185 if ((err = ddf_fun_bind(dev->fun))) {
186 usb_log_error("Device(%d): Failed to register: %s.", dev->address, str_error(err));
187 goto err_usb_dev;
188 }
189
190 return EOK;
191
192err_usb_dev:
193 hcd_ddf_fun_destroy(dev);
194 return err;
195}
196
197static errno_t device_remove(ddf_fun_t *fun, unsigned port)
198{
199 assert(fun);
200 device_t *hub = ddf_fun_data_get(fun);
201 assert(hub);
202 usb_log_debug("Hub `%s' reported removal of device on port %u",
203 ddf_fun_get_name(fun), port);
204
205 device_t *victim = NULL;
206
207 fibril_mutex_lock(&hub->guard);
208 list_foreach(hub->devices, link, device_t, it) {
209 if (it->port == port) {
210 victim = it;
211 break;
212 }
213 }
214 fibril_mutex_unlock(&hub->guard);
215
216 if (!victim) {
217 usb_log_warning("Hub '%s' tried to remove non-existent"
218 " device.", ddf_fun_get_name(fun));
219 return ENOENT;
220 }
221
222 assert(victim->fun);
223 assert(victim->port == port);
224 assert(victim->hub == hub);
225
226 bus_device_gone(victim);
227 return EOK;
228}
229
230/**
231 * Gets description of the device that is calling.
232 *
233 * @param[in] fun Device function.
234 * @param[out] desc Device descriptor to be filled.
235 * @return Error code.
236 */
237static errno_t get_device_description(ddf_fun_t *fun, usb_device_desc_t *desc)
238{
239 assert(fun);
240 device_t *dev = ddf_fun_data_get(fun);
241 assert(dev);
242
243 if (!desc)
244 return EOK;
245
246 *desc = (usb_device_desc_t) {
247 .address = dev->address,
248 .depth = dev->tier,
249 .speed = dev->speed,
250 .handle = ddf_fun_get_handle(fun),
251 .iface = -1,
252 };
253 return EOK;
254}
255
256/**
257 * Transfer issuing interface function.
258 *
259 * @param fun DDF function.
260 * @param target Communication target.
261 * @param dir Communication direction.
262 * @param setup_data Data to use in setup stage (control transfers).
263 * @param data Pointer to data buffer.
264 * @param size Size of the data buffer.
265 * @param callback Function to call on communication end.
266 * @param arg Argument passed to the callback function.
267 * @return Error code.
268 */
269static errno_t transfer(ddf_fun_t *fun, usb_target_t target,
270 usb_direction_t dir, uint64_t setup_data, char *data, size_t size,
271 usbhc_iface_transfer_callback_t callback, void *arg)
272{
273 assert(fun);
274 device_t *dev = ddf_fun_data_get(fun);
275 assert(dev);
276
277 target.address = dev->address;
278
279 if (!usb_target_is_valid(&target))
280 return EINVAL;
281
282 if (size > 0 && data == NULL)
283 return EBADMEM;
284
285 if (!callback && arg)
286 return EBADMEM;
287
288 const char *name = (dir == USB_DIRECTION_IN) ? "READ" : "WRITE";
289
290 return bus_device_send_batch(dev, target, dir,
291 (char *) data, size, setup_data,
292 callback, arg, name);
293}
294
295/** USB device interface */
296static usb_iface_t usb_iface = {
297 .get_my_description = get_device_description,
298};
299
300/** USB host controller interface */
301static usbhc_iface_t usbhc_iface = {
302 .default_address_reservation = default_address_reservation,
303
304 .device_enumerate = device_enumerate,
305 .device_remove = device_remove,
306
307 .register_endpoint = register_endpoint,
308 .unregister_endpoint = unregister_endpoint,
309
310 .transfer = transfer,
311};
312
313/** Standard USB device interface) */
314static ddf_dev_ops_t usb_ops = {
315 .interfaces[USB_DEV_IFACE] = &usb_iface,
316 .interfaces[USBHC_DEV_IFACE] = &usbhc_iface,
317};
318
319
320/* DDF HELPERS */
321
322#define ADD_MATCHID_OR_RETURN(list, sc, str, ...) \
323do { \
324 match_id_t *mid = malloc(sizeof(match_id_t)); \
325 if (!mid) { \
326 clean_match_ids(list); \
327 return ENOMEM; \
328 } \
329 char *id = NULL; \
330 int ret = asprintf(&id, str, ##__VA_ARGS__); \
331 if (ret < 0) { \
332 clean_match_ids(list); \
333 free(mid); \
334 return ENOMEM; \
335 } \
336 mid->score = sc; \
337 mid->id = id; \
338 add_match_id(list, mid); \
339} while (0)
340
341/* This is a copy of lib/usbdev/src/recognise.c */
342static errno_t create_match_ids(match_id_list_t *l,
343 usb_standard_device_descriptor_t *d)
344{
345 assert(l);
346 assert(d);
347
348 if (d->vendor_id != 0) {
349 /* First, with release number. */
350 ADD_MATCHID_OR_RETURN(l, 100,
351 "usb&vendor=%#04x&product=%#04x&release=%x.%x",
352 d->vendor_id, d->product_id, (d->device_version >> 8),
353 (d->device_version & 0xff));
354
355 /* Next, without release number. */
356 ADD_MATCHID_OR_RETURN(l, 90, "usb&vendor=%#04x&product=%#04x",
357 d->vendor_id, d->product_id);
358 }
359
360 /* Class match id */
361 ADD_MATCHID_OR_RETURN(l, 50, "usb&class=%s",
362 usb_str_class(d->device_class));
363
364 /* As a last resort, try fallback driver. */
365 ADD_MATCHID_OR_RETURN(l, 10, "usb&fallback");
366
367 return EOK;
368}
369
370device_t *hcd_ddf_fun_create(hc_device_t *hc, usb_speed_t speed)
371{
372 /* Create DDF function for the new device */
373 ddf_fun_t *fun = ddf_fun_create(hc->ddf_dev, fun_inner, NULL);
374 if (!fun)
375 return NULL;
376
377 ddf_fun_set_ops(fun, &usb_ops);
378
379 /* Create USB device node for the new device */
380 device_t *dev = ddf_fun_data_alloc(fun, hc->bus->device_size);
381 if (!dev) {
382 ddf_fun_destroy(fun);
383 return NULL;
384 }
385
386 bus_device_init(dev, hc->bus);
387 dev->fun = fun;
388 dev->speed = speed;
389 return dev;
390}
391
392void hcd_ddf_fun_destroy(device_t *dev)
393{
394 assert(dev);
395 assert(dev->fun);
396 ddf_fun_destroy(dev->fun);
397}
398
399errno_t hcd_ddf_setup_match_ids(device_t *device, usb_standard_device_descriptor_t *desc)
400{
401 errno_t err;
402 match_id_list_t mids;
403
404 init_match_ids(&mids);
405
406 /* Create match ids from the device descriptor */
407 usb_log_debug("Device(%d): Creating match IDs.", device->address);
408 if ((err = create_match_ids(&mids, desc))) {
409 return err;
410 }
411
412 list_foreach(mids.ids, link, const match_id_t, mid) {
413 ddf_fun_add_match_id(device->fun, mid->id, mid->score);
414 }
415
416 return EOK;
417}
418
419/** Initialize hc structures.
420 *
421 * @param[in] device DDF instance of the device to use.
422 * @param[in] max_speed Maximum supported USB speed.
423 * @param[in] bw available bandwidth.
424 * @param[in] bw_count Function to compute required ep bandwidth.
425 *
426 * @return Error code.
427 * This function does all the ddf work for hc driver.
428 */
429errno_t hcd_ddf_setup_hc(ddf_dev_t *device, size_t size)
430{
431 assert(device);
432
433 hc_device_t *instance = ddf_dev_data_alloc(device, size);
434 if (instance == NULL) {
435 usb_log_error("Failed to allocate HCD ddf structure.");
436 return ENOMEM;
437 }
438 instance->ddf_dev = device;
439
440 errno_t ret = ENOMEM;
441 instance->ctl_fun = ddf_fun_create(device, fun_exposed, "ctl");
442 if (!instance->ctl_fun) {
443 usb_log_error("Failed to create HCD ddf fun.");
444 goto err_destroy_fun;
445 }
446
447 ret = ddf_fun_bind(instance->ctl_fun);
448 if (ret != EOK) {
449 usb_log_error("Failed to bind ctl_fun: %s.", str_error(ret));
450 goto err_destroy_fun;
451 }
452
453 ret = ddf_fun_add_to_category(instance->ctl_fun, USB_HC_CATEGORY);
454 if (ret != EOK) {
455 usb_log_error("Failed to add fun to category: %s.",
456 str_error(ret));
457 ddf_fun_unbind(instance->ctl_fun);
458 goto err_destroy_fun;
459 }
460
461 /* HC should be ok at this point (except it can't do anything) */
462 return EOK;
463
464err_destroy_fun:
465 ddf_fun_destroy(instance->ctl_fun);
466 instance->ctl_fun = NULL;
467 return ret;
468}
469
470void hcd_ddf_clean_hc(hc_device_t *hcd)
471{
472 if (ddf_fun_unbind(hcd->ctl_fun) == EOK)
473 ddf_fun_destroy(hcd->ctl_fun);
474}
475
476/** Call the parent driver with a request to enable interrupt
477 *
478 * @param[in] device Device asking for interrupts
479 * @param[in] inum Interrupt number
480 * @return Error code.
481 */
482errno_t hcd_ddf_enable_interrupt(hc_device_t *hcd, int inum)
483{
484 async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
485 if (parent_sess == NULL)
486 return EIO;
487
488 return hw_res_enable_interrupt(parent_sess, inum);
489}
490
491errno_t hcd_ddf_get_registers(hc_device_t *hcd, hw_res_list_parsed_t *hw_res)
492{
493 async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
494 if (parent_sess == NULL)
495 return EIO;
496
497 hw_res_list_parsed_init(hw_res);
498 const errno_t ret = hw_res_get_list_parsed(parent_sess, hw_res, 0);
499 if (ret != EOK)
500 hw_res_list_parsed_clean(hw_res);
501 return ret;
502}
503
504/**
505 * @}
506 */
Note: See TracBrowser for help on using the repository browser.