Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright 2016 NXP
3 : : */
4 : :
5 : : #include <stdio.h>
6 : : #include <string.h>
7 : : #include <sys/queue.h>
8 : :
9 : : #include <bus_driver.h>
10 : : #include <rte_debug.h>
11 : : #include <rte_devargs.h>
12 : : #include <rte_kvargs.h>
13 : : #include <rte_string_fns.h>
14 : : #include <rte_errno.h>
15 : :
16 : : #include <eal_export.h>
17 : : #include "eal_private.h"
18 : :
19 : : static struct rte_bus_list rte_bus_list =
20 : : TAILQ_HEAD_INITIALIZER(rte_bus_list);
21 : :
22 : : RTE_EXPORT_SYMBOL(rte_bus_name)
23 : : const char *
24 : 40569 : rte_bus_name(const struct rte_bus *bus)
25 : : {
26 : 40569 : return bus->name;
27 : : }
28 : :
29 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_register)
30 : : void
31 : 3311 : rte_bus_register(struct rte_bus *bus)
32 : : {
33 [ - + ]: 3311 : RTE_VERIFY(bus);
34 [ + - - + ]: 3311 : RTE_VERIFY(rte_bus_name(bus) && strlen(rte_bus_name(bus)));
35 : : /* A bus should mandatorily have the scan implemented */
36 [ - + ]: 3311 : RTE_VERIFY(bus->scan);
37 [ - + ]: 3311 : RTE_VERIFY(bus->probe);
38 [ - + ]: 3311 : RTE_VERIFY(bus->find_device);
39 : :
40 : : /* A bus providing probe_device requires match. */
41 [ + - - + ]: 3311 : RTE_VERIFY(!bus->probe_device || bus->match);
42 : :
43 : 3311 : TAILQ_INIT(&bus->device_list);
44 : 3311 : TAILQ_INIT(&bus->driver_list);
45 : 3311 : TAILQ_INSERT_TAIL(&rte_bus_list, bus, next);
46 : 3311 : EAL_LOG(DEBUG, "Registered [%s] bus.", rte_bus_name(bus));
47 : 3311 : }
48 : :
49 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_unregister)
50 : : void
51 : 0 : rte_bus_unregister(struct rte_bus *bus)
52 : : {
53 [ # # ]: 0 : TAILQ_REMOVE(&rte_bus_list, bus, next);
54 : 0 : EAL_LOG(DEBUG, "Unregistered [%s] bus.", rte_bus_name(bus));
55 : 0 : }
56 : :
57 : : /* Scan all the buses for registered devices */
58 : : RTE_EXPORT_SYMBOL(rte_bus_scan)
59 : : int
60 : 225 : rte_bus_scan(void)
61 : : {
62 : : int ret;
63 : : struct rte_bus *bus = NULL;
64 : :
65 [ + + ]: 2700 : TAILQ_FOREACH(bus, &rte_bus_list, next) {
66 : 2475 : ret = bus->scan();
67 [ - + ]: 2475 : if (ret)
68 : 0 : EAL_LOG(ERR, "Scan for (%s) bus failed.",
69 : : rte_bus_name(bus));
70 : : }
71 : :
72 : 225 : return 0;
73 : : }
74 : :
75 : : /*
76 : : * Generic probe function for buses.
77 : : * Iterates through all devices on the bus, finds matching drivers,
78 : : * and calls bus->probe_device() for each device.
79 : : */
80 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_generic_probe)
81 : : int
82 : 2420 : rte_bus_generic_probe(struct rte_bus *bus)
83 : : {
84 : : size_t probed = 0, failed = 0;
85 : : struct rte_device *dev;
86 : :
87 [ + + ]: 9569 : TAILQ_FOREACH(dev, &bus->device_list, next) {
88 : : struct rte_driver *drv = NULL;
89 : : bool was_probed;
90 : : int ret;
91 : :
92 [ - + ]: 7149 : if (rte_bus_device_is_ignored(bus, dev->name))
93 : 0 : continue;
94 : :
95 [ - + - - ]: 7149 : if (rte_dev_is_probed(dev) && !bus->allow_multi_probe) {
96 : 0 : EAL_LOG(INFO, "Device %s is already probed", dev->name);
97 : 0 : continue;
98 : : }
99 : :
100 : 7149 : probed++;
101 : 7332 : next_driver:
102 : 7332 : drv = rte_bus_find_driver(bus, drv, dev);
103 [ + + ]: 7332 : if (drv == NULL)
104 : 7137 : continue;
105 : :
106 : 195 : was_probed = rte_dev_is_probed(dev);
107 [ + - ]: 195 : if (!was_probed)
108 : 195 : dev->driver = drv;
109 : 195 : ret = bus->probe_device(drv, dev);
110 [ + + ]: 195 : if (!was_probed && ret != 0)
111 : 183 : dev->driver = NULL;
112 : :
113 [ + + ]: 195 : if (ret > 0)
114 : 183 : goto next_driver;
115 : :
116 [ - + ]: 12 : if (ret < 0) {
117 [ # # ]: 0 : if (ret == -EEXIST)
118 : 0 : continue;
119 : 0 : EAL_LOG(ERR, "Failed to probe device %s", dev->name);
120 : 0 : failed++;
121 : : }
122 : : }
123 : :
124 [ + - ]: 2420 : return (probed && probed == failed) ? -1 : 0;
125 : : }
126 : :
127 : : /*
128 : : * Generic cleanup function for buses.
129 : : * Iterates through all devices on the bus, unplugs probed devices,
130 : : * removes devargs, removes devices from the bus list, and frees device structures.
131 : : */
132 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_generic_cleanup)
133 : : int
134 : 2058 : rte_bus_generic_cleanup(struct rte_bus *bus)
135 : : {
136 : : struct rte_device *dev;
137 : : int error = 0;
138 : :
139 [ - + ]: 2058 : RTE_VERIFY(bus->free_device);
140 [ - + ]: 2058 : RTE_VERIFY(bus->unplug_device);
141 : :
142 [ + + ]: 9288 : while ((dev = TAILQ_FIRST(&bus->device_list)) != NULL) {
143 [ + + ]: 7230 : if (rte_dev_is_probed(dev)) {
144 [ - + ]: 14 : if (bus->unplug_device(dev) < 0) {
145 : 0 : rte_errno = errno;
146 : : error = -1;
147 : : }
148 : : }
149 : :
150 : 7230 : rte_devargs_remove(dev->devargs);
151 : 7230 : rte_bus_remove_device(bus, dev);
152 : 7230 : bus->free_device(dev);
153 : : }
154 : :
155 : 2058 : return error;
156 : : }
157 : :
158 : : /* Probe all devices of all buses */
159 : : RTE_EXPORT_SYMBOL(rte_bus_probe)
160 : : int
161 : 220 : rte_bus_probe(void)
162 : : {
163 : : int ret;
164 : : struct rte_bus *bus, *vbus = NULL;
165 : :
166 [ + + ]: 2640 : TAILQ_FOREACH(bus, &rte_bus_list, next) {
167 [ + + ]: 2420 : if (!strcmp(rte_bus_name(bus), "vdev")) {
168 : : vbus = bus;
169 : 220 : continue;
170 : : }
171 : :
172 : 2200 : ret = bus->probe(bus);
173 [ - + ]: 2200 : if (ret)
174 : 0 : EAL_LOG(ERR, "Bus (%s) probe failed.",
175 : : rte_bus_name(bus));
176 : : }
177 : :
178 [ + - ]: 220 : if (vbus) {
179 : 220 : ret = vbus->probe(vbus);
180 [ - + ]: 220 : if (ret)
181 : 0 : EAL_LOG(ERR, "Bus (%s) probe failed.",
182 : : rte_bus_name(vbus));
183 : : }
184 : :
185 : 220 : return 0;
186 : : }
187 : :
188 : : /* Clean up all devices of all buses */
189 : : int
190 : 294 : eal_bus_cleanup(void)
191 : : {
192 : : int ret = 0;
193 : : struct rte_bus *bus;
194 : :
195 [ + + ]: 3528 : TAILQ_FOREACH(bus, &rte_bus_list, next) {
196 [ + + ]: 3234 : if (bus->cleanup == NULL)
197 : 588 : continue;
198 [ - + ]: 2646 : if (bus->cleanup(bus) != 0)
199 : : ret = -1;
200 : : }
201 : :
202 : 294 : return ret;
203 : : }
204 : :
205 : : /* Dump information of a single bus */
206 : : static int
207 : 0 : bus_dump_one(FILE *f, struct rte_bus *bus)
208 : : {
209 : : int ret;
210 : :
211 : : /* For now, dump only the bus name */
212 : 0 : ret = fprintf(f, " %s\n", rte_bus_name(bus));
213 : :
214 : : /* Error in case of inability in writing to stream */
215 : : if (ret < 0)
216 : : return ret;
217 : :
218 : : return 0;
219 : : }
220 : :
221 : : RTE_EXPORT_SYMBOL(rte_bus_dump)
222 : : void
223 : 0 : rte_bus_dump(FILE *f)
224 : : {
225 : : int ret;
226 : : struct rte_bus *bus;
227 : :
228 [ # # ]: 0 : TAILQ_FOREACH(bus, &rte_bus_list, next) {
229 : 0 : ret = bus_dump_one(f, bus);
230 [ # # ]: 0 : if (ret) {
231 : 0 : EAL_LOG(ERR, "Unable to write to stream (%d)",
232 : : ret);
233 : 0 : break;
234 : : }
235 : : }
236 : 0 : }
237 : :
238 : : RTE_EXPORT_SYMBOL(rte_bus_find)
239 : : struct rte_bus *
240 : 105 : rte_bus_find(const struct rte_bus *start, rte_bus_cmp_t cmp,
241 : : const void *data)
242 : : {
243 : : struct rte_bus *bus;
244 : :
245 [ + + ]: 105 : if (start != NULL)
246 : 3 : bus = TAILQ_NEXT(start, next);
247 : : else
248 : 102 : bus = TAILQ_FIRST(&rte_bus_list);
249 [ + + ]: 1044 : while (bus != NULL) {
250 [ + + ]: 986 : if (cmp(bus, data) == 0)
251 : : break;
252 : 939 : bus = TAILQ_NEXT(bus, next);
253 : : }
254 : 105 : return bus;
255 : : }
256 : :
257 : : static int
258 : 0 : cmp_rte_device(const struct rte_device *dev1, const void *_dev2)
259 : : {
260 : : const struct rte_device *dev2 = _dev2;
261 : :
262 : 0 : return dev1 != dev2;
263 : : }
264 : :
265 : : static int
266 : 0 : bus_find_device(const struct rte_bus *bus, const void *_dev)
267 : : {
268 : : struct rte_device *dev;
269 : :
270 : 0 : dev = bus->find_device(bus, NULL, cmp_rte_device, _dev);
271 : 0 : return dev == NULL;
272 : : }
273 : :
274 : : RTE_EXPORT_SYMBOL(rte_bus_find_by_device)
275 : : struct rte_bus *
276 : 0 : rte_bus_find_by_device(const struct rte_device *dev)
277 : : {
278 : 0 : return rte_bus_find(NULL, bus_find_device, (const void *)dev);
279 : : }
280 : :
281 : : static int
282 : 152 : cmp_bus_name(const struct rte_bus *bus, const void *_name)
283 : : {
284 : : const char *name = _name;
285 : :
286 : 152 : return strcmp(rte_bus_name(bus), name);
287 : : }
288 : :
289 : : RTE_EXPORT_SYMBOL(rte_bus_find_by_name)
290 : : struct rte_bus *
291 : 18 : rte_bus_find_by_name(const char *busname)
292 : : {
293 : 18 : return rte_bus_find(NULL, cmp_bus_name, (const void *)busname);
294 : : }
295 : :
296 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_find_devargs)
297 : : struct rte_devargs *
298 : 25878 : rte_bus_find_devargs(const struct rte_bus *bus, const char *name)
299 : : {
300 : 25878 : rte_bus_dev_compare_t cmp = bus->dev_compare;
301 : 25878 : const char *bus_name = rte_bus_name(bus);
302 : : struct rte_devargs *devargs;
303 : :
304 [ + + ]: 25878 : if (cmp == NULL)
305 : : cmp = strcmp;
306 : :
307 [ + + ]: 26478 : RTE_EAL_DEVARGS_FOREACH(bus_name, devargs) {
308 : 616 : const char *devargs_name = devargs->name;
309 : :
310 : : /* The name in the devargs is usually prefixed with <bus>: */
311 [ - + ]: 616 : if (strncmp(devargs_name, bus_name, strlen(bus_name)) == 0)
312 : 0 : devargs_name += strlen(bus_name) + 1;
313 [ + + ]: 616 : if (cmp(name, devargs_name) != 0)
314 : : continue;
315 : 16 : EAL_LOG(DEBUG, "found devargs for %s:%s", bus_name, name);
316 : 16 : return devargs;
317 : : }
318 : :
319 : 25862 : EAL_LOG(DEBUG, "no devargs for %s:%s", bus_name, name);
320 : 25862 : return NULL;
321 : : }
322 : :
323 : : static int
324 : 372 : bus_can_parse(const struct rte_bus *bus, const void *_name)
325 : : {
326 : : const char *name = _name;
327 : :
328 [ + - + + ]: 372 : return !(bus->parse && bus->parse(name, NULL) == 0);
329 : : }
330 : :
331 : : struct rte_bus *
332 [ + + ]: 42 : rte_bus_find_by_device_name(const char *str)
333 : : {
334 : : char name[RTE_DEV_NAME_MAX_LEN];
335 : : char *c;
336 : :
337 : : strlcpy(name, str, sizeof(name));
338 : 42 : c = strchr(name, ',');
339 [ + + ]: 42 : if (c != NULL)
340 : 1 : c[0] = '\0';
341 : 42 : return rte_bus_find(NULL, bus_can_parse, name);
342 : : }
343 : :
344 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_device_is_ignored)
345 : : bool
346 : 17923 : rte_bus_device_is_ignored(const struct rte_bus *bus, const char *dev_name)
347 : : {
348 : 17923 : const struct internal_config *internal_conf = eal_get_internal_configuration();
349 : 17923 : struct rte_devargs *devargs = rte_bus_find_devargs(bus, dev_name);
350 : 17923 : enum rte_bus_scan_mode scan_mode = bus->conf.scan_mode;
351 : :
352 [ + + ]: 17923 : if (scan_mode == RTE_BUS_SCAN_UNDEFINED) {
353 [ + + ]: 17489 : if (internal_conf->no_auto_probing != 0)
354 : : scan_mode = RTE_BUS_SCAN_ALLOWLIST;
355 : : else
356 : : scan_mode = RTE_BUS_SCAN_BLOCKLIST;
357 : : }
358 : :
359 [ + + ]: 434 : if (scan_mode == RTE_BUS_SCAN_ALLOWLIST) {
360 [ + + - + ]: 390 : if (devargs && devargs->policy == RTE_DEV_ALLOWED)
361 : : return false;
362 : : } else {
363 [ + + + - ]: 17533 : if (devargs == NULL || devargs->policy != RTE_DEV_BLOCKED)
364 : : return false;
365 : : }
366 : :
367 : 377 : EAL_LOG(DEBUG, "device %s:%s is ignored", rte_bus_name(bus), dev_name);
368 : 377 : return true;
369 : : }
370 : :
371 : : /*
372 : : * Get iommu class of devices on the bus.
373 : : */
374 : : RTE_EXPORT_SYMBOL(rte_bus_get_iommu_class)
375 : : enum rte_iova_mode
376 : 225 : rte_bus_get_iommu_class(void)
377 : : {
378 : : enum rte_iova_mode mode = RTE_IOVA_DC;
379 : : bool buses_want_va = false;
380 : : bool buses_want_pa = false;
381 : : struct rte_bus *bus;
382 : :
383 [ + + ]: 2700 : TAILQ_FOREACH(bus, &rte_bus_list, next) {
384 : : enum rte_iova_mode bus_iova_mode;
385 : :
386 [ + + ]: 2475 : if (bus->get_iommu_class == NULL)
387 : 675 : continue;
388 : :
389 : 1800 : bus_iova_mode = bus->get_iommu_class();
390 [ + + + - ]: 1801 : EAL_LOG(DEBUG, "Bus %s wants IOVA as '%s'",
391 : : rte_bus_name(bus),
392 : : bus_iova_mode == RTE_IOVA_DC ? "DC" :
393 : : (bus_iova_mode == RTE_IOVA_PA ? "PA" : "VA"));
394 [ + - ]: 1800 : if (bus_iova_mode == RTE_IOVA_PA) {
395 : : buses_want_pa = true;
396 : : if (!RTE_IOVA_IN_MBUF)
397 : : EAL_LOG(WARNING,
398 : : "Bus %s wants IOVA as PA not compatible with 'enable_iova_as_pa=false' build option.",
399 : : rte_bus_name(bus));
400 [ + + ]: 1800 : } else if (bus_iova_mode == RTE_IOVA_VA)
401 : : buses_want_va = true;
402 : : }
403 [ + + ]: 225 : if (buses_want_va && !buses_want_pa) {
404 : : mode = RTE_IOVA_VA;
405 [ + - ]: 224 : } else if (buses_want_pa && !buses_want_va) {
406 : : mode = RTE_IOVA_PA;
407 : : } else {
408 : : mode = RTE_IOVA_DC;
409 [ - + ]: 224 : if (buses_want_va) {
410 : 0 : EAL_LOG(WARNING, "Some buses want 'VA' but forcing 'DC' because other buses want 'PA'.");
411 : 0 : EAL_LOG(WARNING, "Depending on the final decision by the EAL, not all buses may be able to initialize.");
412 : : }
413 : : }
414 : :
415 : 225 : return mode;
416 : : }
417 : :
418 : : static int
419 : 0 : bus_handle_sigbus(const struct rte_bus *bus,
420 : : const void *failure_addr)
421 : : {
422 : : int ret;
423 : :
424 [ # # ]: 0 : if (!bus->sigbus_handler)
425 : : return -1;
426 : :
427 : 0 : ret = bus->sigbus_handler(failure_addr);
428 : :
429 : : /* find bus but handle failed, keep the errno be set. */
430 [ # # # # ]: 0 : if (ret < 0 && rte_errno == 0)
431 : 0 : rte_errno = ENOTSUP;
432 : :
433 : 0 : return ret > 0;
434 : : }
435 : :
436 : : int
437 : 0 : rte_bus_sigbus_handler(const void *failure_addr)
438 : : {
439 : : struct rte_bus *bus;
440 : :
441 : : int ret = 0;
442 : 0 : int old_errno = rte_errno;
443 : :
444 : 0 : rte_errno = 0;
445 : :
446 : 0 : bus = rte_bus_find(NULL, bus_handle_sigbus, failure_addr);
447 : : /* can not find bus. */
448 [ # # ]: 0 : if (!bus)
449 : : return 1;
450 : : /* find bus but handle failed, pass on the new errno. */
451 [ # # ]: 0 : else if (rte_errno != 0)
452 : : return -1;
453 : :
454 : : /* restore the old errno. */
455 : 0 : rte_errno = old_errno;
456 : :
457 : 0 : return ret;
458 : : }
459 : :
460 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_add_device)
461 : : void
462 : 258 : rte_bus_add_device(struct rte_bus *bus, struct rte_device *dev)
463 : : {
464 : 258 : TAILQ_INSERT_TAIL(&bus->device_list, dev, next);
465 : 258 : dev->bus = bus;
466 : 258 : }
467 : :
468 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_remove_device)
469 : : void
470 : 7287 : rte_bus_remove_device(struct rte_bus *bus, struct rte_device *dev)
471 : : {
472 [ + + ]: 7287 : TAILQ_REMOVE(&bus->device_list, dev, next);
473 : 7287 : dev->bus = NULL;
474 : 7287 : }
475 : :
476 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_insert_device)
477 : : void
478 : 7029 : rte_bus_insert_device(struct rte_bus *bus,
479 : : struct rte_device *exist_dev,
480 : : struct rte_device *new_dev)
481 : : {
482 : 7029 : TAILQ_INSERT_BEFORE(exist_dev, new_dev, next);
483 : 7029 : new_dev->bus = bus;
484 : 7029 : }
485 : :
486 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_generic_find_device)
487 : : struct rte_device *
488 : 10 : rte_bus_generic_find_device(const struct rte_bus *bus, const struct rte_device *start,
489 : : rte_dev_cmp_t cmp, const void *data)
490 : : {
491 : : struct rte_device *dev;
492 : :
493 [ + + ]: 10 : if (start != NULL)
494 : 2 : dev = TAILQ_NEXT(start, next);
495 : : else
496 : 8 : dev = TAILQ_FIRST(&bus->device_list);
497 [ + + ]: 14 : while (dev != NULL) {
498 [ + + ]: 12 : if (cmp(dev, data) == 0)
499 : 8 : return dev;
500 : 4 : dev = TAILQ_NEXT(dev, next);
501 : : }
502 : : return NULL;
503 : : }
504 : :
505 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_add_driver)
506 : : void
507 : 46956 : rte_bus_add_driver(struct rte_bus *bus, struct rte_driver *driver)
508 : : {
509 : 46956 : TAILQ_INSERT_TAIL(&bus->driver_list, driver, next);
510 : 46956 : driver->bus = bus;
511 : 46956 : }
512 : :
513 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_remove_driver)
514 : : void
515 : 903 : rte_bus_remove_driver(struct rte_bus *bus, struct rte_driver *driver)
516 : : {
517 [ + + ]: 903 : TAILQ_REMOVE(&bus->driver_list, driver, next);
518 : 903 : driver->bus = NULL;
519 : 903 : }
520 : :
521 : : static int
522 : 2 : bus_dev_match_by_name(const struct rte_device *dev, const void *_kvlist)
523 : : {
524 : : const struct rte_kvargs *kvlist = _kvlist;
525 : : const char *name;
526 : :
527 [ + - ]: 2 : if (kvlist == NULL)
528 : : return 0;
529 : :
530 : 2 : name = rte_kvargs_get(kvlist, "name");
531 [ - + - - ]: 2 : if (name != NULL && strcmp(name, dev->name))
532 : 0 : return -1;
533 : :
534 : : return 0;
535 : : }
536 : :
537 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_generic_dev_iterate)
538 : : void *
539 : 3 : rte_bus_generic_dev_iterate(const struct rte_bus *bus,
540 : : const void *start,
541 : : const char *str,
542 : : const struct rte_dev_iterator *it __rte_unused)
543 : : {
544 : : static const char * const params_keys[] = { "name", NULL };
545 : : struct rte_kvargs *kvargs = NULL;
546 : : struct rte_device *dev;
547 : :
548 [ + - ]: 3 : if (str != NULL) {
549 : 3 : kvargs = rte_kvargs_parse(str, params_keys);
550 [ - + ]: 3 : if (kvargs == NULL) {
551 : 0 : rte_errno = EINVAL;
552 : 0 : return NULL;
553 : : }
554 : : }
555 : :
556 : 3 : dev = rte_bus_generic_find_device(bus, start, bus_dev_match_by_name, kvargs);
557 : 3 : rte_kvargs_free(kvargs);
558 : 3 : return dev;
559 : : }
560 : :
561 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_bus_find_driver)
562 : : struct rte_driver *
563 : 7391 : rte_bus_find_driver(const struct rte_bus *bus, const struct rte_driver *start,
564 : : const struct rte_device *dev)
565 : : {
566 : : struct rte_driver *drv;
567 : :
568 [ + + ]: 7391 : if (start != NULL)
569 : 183 : drv = TAILQ_NEXT(start, next);
570 : : else
571 : 7208 : drv = TAILQ_FIRST(&bus->driver_list);
572 [ + + ]: 764572 : while (drv != NULL) {
573 [ + + ]: 757435 : if (bus->match(drv, dev))
574 : : break;
575 : 757181 : drv = TAILQ_NEXT(drv, next);
576 : : }
577 : 7391 : return drv;
578 : : }
|