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

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

libdrv: usb iface callbacks joined

In addition to handle and current interface, it is good for the device
to know its address and speed. Also, it is expected to add some other
fields (stats, info tied to devices of specific speed and so on) in the
future. Instead of adding yet another call, join those two and let HC
fill a description structure.

  • Property mode set to 100644
File size: 13.1 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 int 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 int 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 int 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 int 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 int err;
155
156 usb_log_debug("Hub %d reported a new %s speed device on port: %u",
157 hub->address, usb_str_speed(speed), port);
158
159 device_t *dev = hcd_ddf_fun_create(hcd, speed);
160 if (!dev) {
161 usb_log_error("Failed to create USB device function.");
162 return ENOMEM;
163 }
164
165 dev->hub = hub;
166 dev->port = port;
167 dev->speed = speed;
168
169 if ((err = bus_device_enumerate(dev))) {
170 usb_log_error("Failed to initialize USB dev memory structures.");
171 goto err_usb_dev;
172 }
173
174 /* If the driver didn't name the dev when enumerating,
175 * do it in some generic way.
176 */
177 if (!ddf_fun_get_name(dev->fun)) {
178 bus_device_set_default_name(dev);
179 }
180
181 if ((err = ddf_fun_bind(dev->fun))) {
182 usb_log_error("Device(%d): Failed to register: %s.", dev->address, str_error(err));
183 goto err_usb_dev;
184 }
185
186 return EOK;
187
188err_usb_dev:
189 hcd_ddf_fun_destroy(dev);
190 return err;
191}
192
193static int device_remove(ddf_fun_t *fun, unsigned port)
194{
195 assert(fun);
196 device_t *hub = ddf_fun_data_get(fun);
197 assert(hub);
198 usb_log_debug("Hub `%s' reported removal of device on port %u",
199 ddf_fun_get_name(fun), port);
200
201 device_t *victim = NULL;
202
203 fibril_mutex_lock(&hub->guard);
204 list_foreach(hub->devices, link, device_t, it) {
205 if (it->port == port) {
206 victim = it;
207 break;
208 }
209 }
210 fibril_mutex_unlock(&hub->guard);
211
212 if (!victim) {
213 usb_log_warning("Hub '%s' tried to remove non-existant"
214 " device.", ddf_fun_get_name(fun));
215 return ENOENT;
216 }
217
218 assert(victim->fun);
219 assert(victim->port == port);
220 assert(victim->hub == hub);
221
222 bus_device_gone(victim);
223 return EOK;
224}
225
226/**
227 * Gets description of the device that is calling.
228 *
229 * @param[in] fun Device function.
230 * @param[out] desc Device descriptor to be filled.
231 * @return Error code.
232 */
233static int get_device_description(ddf_fun_t *fun, usb_device_desc_t *desc)
234{
235 assert(fun);
236 device_t *dev = ddf_fun_data_get(fun);
237 assert(dev);
238
239 if (!desc)
240 return EOK;
241
242 *desc = (usb_device_desc_t) {
243 .address = dev->address,
244 .speed = dev->speed,
245 .handle = ddf_fun_get_handle(fun),
246 .iface = -1,
247 };
248 return EOK;
249}
250
251/** Inbound communication interface function.
252 * @param fun DDF function.
253 * @param target Communication target.
254 * @param setup_data Data to use in setup stage (control transfers).
255 * @param data Pointer to data buffer.
256 * @param size Size of the data buffer.
257 * @param callback Function to call on communication end.
258 * @param arg Argument passed to the callback function.
259 * @return Error code.
260 */
261static int dev_read(ddf_fun_t *fun, usb_target_t target,
262 uint64_t setup_data, char *data, size_t size,
263 usbhc_iface_transfer_callback_t callback, void *arg)
264{
265 assert(fun);
266 device_t *dev = ddf_fun_data_get(fun);
267 assert(dev);
268
269 target.address = dev->address;
270
271 return bus_device_send_batch(dev, target, USB_DIRECTION_IN,
272 data, size, setup_data,
273 callback, arg, "READ");
274}
275
276/** Outbound communication interface function.
277 * @param fun DDF function.
278 * @param target Communication target.
279 * @param setup_data Data to use in setup stage (control transfers).
280 * @param data Pointer to data buffer.
281 * @param size Size of the data buffer.
282 * @param callback Function to call on communication end.
283 * @param arg Argument passed to the callback function.
284 * @return Error code.
285 */
286static int dev_write(ddf_fun_t *fun, usb_target_t target,
287 uint64_t setup_data, const char *data, size_t size,
288 usbhc_iface_transfer_callback_t callback, void *arg)
289{
290 assert(fun);
291 device_t *dev = ddf_fun_data_get(fun);
292 assert(dev);
293
294 target.address = dev->address;
295
296 return bus_device_send_batch(dev, target, USB_DIRECTION_OUT,
297 (char *) data, size, setup_data,
298 callback, arg, "WRITE");
299}
300
301/** USB device interface */
302static usb_iface_t usb_iface = {
303 .get_my_description = get_device_description,
304};
305
306/** USB host controller interface */
307static usbhc_iface_t usbhc_iface = {
308 .default_address_reservation = default_address_reservation,
309
310 .device_enumerate = device_enumerate,
311 .device_remove = device_remove,
312
313 .register_endpoint = register_endpoint,
314 .unregister_endpoint = unregister_endpoint,
315
316 .read = dev_read,
317 .write = dev_write,
318};
319
320/** Standard USB device interface) */
321static ddf_dev_ops_t usb_ops = {
322 .interfaces[USB_DEV_IFACE] = &usb_iface,
323 .interfaces[USBHC_DEV_IFACE] = &usbhc_iface,
324};
325
326
327/* DDF HELPERS */
328
329#define ADD_MATCHID_OR_RETURN(list, sc, str, ...) \
330do { \
331 match_id_t *mid = malloc(sizeof(match_id_t)); \
332 if (!mid) { \
333 clean_match_ids(list); \
334 return ENOMEM; \
335 } \
336 char *id = NULL; \
337 int ret = asprintf(&id, str, ##__VA_ARGS__); \
338 if (ret < 0) { \
339 clean_match_ids(list); \
340 free(mid); \
341 return ENOMEM; \
342 } \
343 mid->score = sc; \
344 mid->id = id; \
345 add_match_id(list, mid); \
346} while (0)
347
348/* This is a copy of lib/usbdev/src/recognise.c */
349static int create_match_ids(match_id_list_t *l,
350 usb_standard_device_descriptor_t *d)
351{
352 assert(l);
353 assert(d);
354
355 if (d->vendor_id != 0) {
356 /* First, with release number. */
357 ADD_MATCHID_OR_RETURN(l, 100,
358 "usb&vendor=%#04x&product=%#04x&release=%x.%x",
359 d->vendor_id, d->product_id, (d->device_version >> 8),
360 (d->device_version & 0xff));
361
362 /* Next, without release number. */
363 ADD_MATCHID_OR_RETURN(l, 90, "usb&vendor=%#04x&product=%#04x",
364 d->vendor_id, d->product_id);
365 }
366
367 /* Class match id */
368 ADD_MATCHID_OR_RETURN(l, 50, "usb&class=%s",
369 usb_str_class(d->device_class));
370
371 /* As a last resort, try fallback driver. */
372 ADD_MATCHID_OR_RETURN(l, 10, "usb&fallback");
373
374 return EOK;
375}
376
377device_t *hcd_ddf_fun_create(hc_device_t *hc, usb_speed_t speed)
378{
379 /* Create DDF function for the new device */
380 ddf_fun_t *fun = ddf_fun_create(hc->ddf_dev, fun_inner, NULL);
381 if (!fun)
382 return NULL;
383
384 ddf_fun_set_ops(fun, &usb_ops);
385
386 /* Create USB device node for the new device */
387 device_t *dev = ddf_fun_data_alloc(fun, hc->bus->device_size);
388 if (!dev) {
389 ddf_fun_destroy(fun);
390 return NULL;
391 }
392
393 bus_device_init(dev, hc->bus);
394 dev->fun = fun;
395 dev->speed = speed;
396 return dev;
397}
398
399void hcd_ddf_fun_destroy(device_t *dev)
400{
401 assert(dev);
402 assert(dev->fun);
403 ddf_fun_destroy(dev->fun);
404}
405
406int hcd_ddf_setup_match_ids(device_t *device, usb_standard_device_descriptor_t *desc)
407{
408 int err;
409 match_id_list_t mids;
410
411 init_match_ids(&mids);
412
413 /* Create match ids from the device descriptor */
414 usb_log_debug("Device(%d): Creating match IDs.", device->address);
415 if ((err = create_match_ids(&mids, desc))) {
416 return err;
417 }
418
419 list_foreach(mids.ids, link, const match_id_t, mid) {
420 ddf_fun_add_match_id(device->fun, mid->id, mid->score);
421 }
422
423 return EOK;
424}
425
426/** Initialize hc structures.
427 *
428 * @param[in] device DDF instance of the device to use.
429 * @param[in] max_speed Maximum supported USB speed.
430 * @param[in] bw available bandwidth.
431 * @param[in] bw_count Function to compute required ep bandwidth.
432 *
433 * @return Error code.
434 * This function does all the ddf work for hc driver.
435 */
436int hcd_ddf_setup_hc(ddf_dev_t *device, size_t size)
437{
438 assert(device);
439
440 hc_device_t *instance = ddf_dev_data_alloc(device, size);
441 if (instance == NULL) {
442 usb_log_error("Failed to allocate HCD ddf structure.");
443 return ENOMEM;
444 }
445 instance->ddf_dev = device;
446
447 int ret = ENOMEM;
448 instance->ctl_fun = ddf_fun_create(device, fun_exposed, "ctl");
449 if (!instance->ctl_fun) {
450 usb_log_error("Failed to create HCD ddf fun.");
451 goto err_destroy_fun;
452 }
453
454 ret = ddf_fun_bind(instance->ctl_fun);
455 if (ret != EOK) {
456 usb_log_error("Failed to bind ctl_fun: %s.", str_error(ret));
457 goto err_destroy_fun;
458 }
459
460 ret = ddf_fun_add_to_category(instance->ctl_fun, USB_HC_CATEGORY);
461 if (ret != EOK) {
462 usb_log_error("Failed to add fun to category: %s.",
463 str_error(ret));
464 ddf_fun_unbind(instance->ctl_fun);
465 goto err_destroy_fun;
466 }
467
468 /* HC should be ok at this point (except it can't do anything) */
469 return EOK;
470
471err_destroy_fun:
472 ddf_fun_destroy(instance->ctl_fun);
473 instance->ctl_fun = NULL;
474 return ret;
475}
476
477void hcd_ddf_clean_hc(hc_device_t *hcd)
478{
479 if (ddf_fun_unbind(hcd->ctl_fun) == EOK)
480 ddf_fun_destroy(hcd->ctl_fun);
481}
482
483/** Call the parent driver with a request to enable interrupt
484 *
485 * @param[in] device Device asking for interrupts
486 * @param[in] inum Interrupt number
487 * @return Error code.
488 */
489int hcd_ddf_enable_interrupt(hc_device_t *hcd, int inum)
490{
491 async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
492 if (parent_sess == NULL)
493 return EIO;
494
495 return hw_res_enable_interrupt(parent_sess, inum);
496}
497
498int hcd_ddf_get_registers(hc_device_t *hcd, hw_res_list_parsed_t *hw_res)
499{
500 async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
501 if (parent_sess == NULL)
502 return EIO;
503
504 hw_res_list_parsed_init(hw_res);
505 const int ret = hw_res_get_list_parsed(parent_sess, hw_res, 0);
506 if (ret != EOK)
507 hw_res_list_parsed_clean(hw_res);
508 return ret;
509}
510
511/**
512 * @}
513 */
Note: See TracBrowser for help on using the repository browser.