summaryrefslogtreecommitdiffstats
path: root/cpukit/libdrvmgr/drvmgr.c
blob: 43148fd76af0200f2da268c2540cc96b45a6c3f9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
/* Driver Manager Interface Implementation.
 *
 * COPYRIGHT (c) 2009 Cobham Gaisler AB.
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <drvmgr/drvmgr.h>
#include <drvmgr/drvmgr_confdefs.h>

#include <rtems/sysinit.h>

#include "drvmgr_internal.h"

/* Enable debugging */
/*#define DEBUG 1*/

#ifdef DEBUG
#define DBG(x...) printk(x)
#else
#define DBG(x...)
#endif

struct drvmgr drvmgr = {
	.level =		0,
	.initializing_objs =	0,
	.lock =			API_MUTEX_INITIALIZER("_Drvmgr"),
	.root_dev =		{0},
	.root_drv =		NULL,

	.drivers =	LIST_INITIALIZER(struct drvmgr_drv, next),

	.buses = {
		LIST_INITIALIZER(struct drvmgr_bus, next),
		LIST_INITIALIZER(struct drvmgr_bus, next),
		LIST_INITIALIZER(struct drvmgr_bus, next),
		LIST_INITIALIZER(struct drvmgr_bus, next),
		LIST_INITIALIZER(struct drvmgr_bus, next),
	},
	.buses_inactive =	LIST_INITIALIZER(struct drvmgr_bus, next),

	.devices = {
		LIST_INITIALIZER(struct drvmgr_dev, next),
		LIST_INITIALIZER(struct drvmgr_dev, next),
		LIST_INITIALIZER(struct drvmgr_dev, next),
		LIST_INITIALIZER(struct drvmgr_dev, next),
		LIST_INITIALIZER(struct drvmgr_dev, next),
	},
	.devices_inactive =	LIST_INITIALIZER(struct drvmgr_dev, next),
};

static int do_bus_init(
	struct drvmgr *mgr,
	struct drvmgr_bus *bus,
	int level);
static int do_dev_init(
	struct drvmgr *mgr,
	struct drvmgr_dev *dev,
	int level);

/* DRIVER MANAGER */

void _DRV_Manager_init_level(int level)
{
	struct drvmgr *mgr = &drvmgr;

	if (mgr->level >= level)
		return;

	/* Set new Level */
	mgr->level = level;

	/* Initialize buses and devices into this new level */
	drvmgr_init_update();
}

/* Initialize Data structures of the driver manager and call driver
 * register functions configured by the user.
 */
void _DRV_Manager_initialization(void)
{
	drvmgr_drv_reg_func *drvreg;

	/* Call driver register functions. */
	drvreg = &drvmgr_drivers[0];
	while (*drvreg) {
		/* Make driver register */
		(*drvreg)();
		drvreg++;
	}
}

/* Take ready devices and buses into the correct init level step by step.
 * Once a bus or a device has been registered there is no turning
 * back - they are taken to the level of the driver manager.
 */
void drvmgr_init_update(void)
{
	struct drvmgr *mgr = &drvmgr;
	struct drvmgr_bus *bus;
	struct drvmgr_dev *dev;
	int bus_might_been_registered;
	int level;

	/* "Lock" to make sure we don't use up the stack and that the lists
	 * remain consistent.
	 */
	DRVMGR_LOCK_WRITE();
	if (mgr->initializing_objs || (mgr->level == 0))
		goto out;
	mgr->initializing_objs = 1;

	/* Take all buses and devices ready into the same stage
	 * as the driver manager global level.
	 */
	for (level = 0; level < mgr->level; level++) {

		bus_might_been_registered = 0;

		/* Take buses into next level */

		while ((bus = BUS_LIST_HEAD(&mgr->buses[level])) != NULL) {

			/* Remove first in the list (will be inserted in
			 * appropriate list by do_bus_init())
			 */
			drvmgr_list_remove_head(&mgr->buses[level]);

			DRVMGR_UNLOCK();

			/* Initialize Bus, this will register devices on
			 * the bus. Take bus into next level.
			 */
			do_bus_init(mgr, bus, level+1);

			DRVMGR_LOCK_WRITE();
		}

		/* Take devices into next level */
		while ((dev = DEV_LIST_HEAD(&mgr->devices[level])) != NULL) {

			/* Always process first in list */
			dev = DEV_LIST_HEAD(&mgr->devices[level]);

			/* Remove first in the list (will be inserted in
			 * appropriate list by do_dev_init())
			 */
			drvmgr_list_remove_head(&mgr->devices[level]);

			DRVMGR_UNLOCK();

			/* Initialize Device, this may register a new bus */
			do_dev_init(mgr, dev, level+1);

			DRVMGR_LOCK_WRITE();

			bus_might_been_registered = 1;
		}

		/* Make sure all buses registered and ready are taken at
		 * the same time into init level N.
		 */
		if (bus_might_been_registered) {
			level = -1; /* restart loop */
		}
	}

	/* Release bus/device initialization "Lock" */
	mgr->initializing_objs = 0;

out:
	DRVMGR_UNLOCK();
}

/* Take bus into next level */
static int do_bus_init(
	struct drvmgr *mgr,
	struct drvmgr_bus *bus,
	int level)
{
	int (*init)(struct drvmgr_bus *);

	/* If bridge device has failed during initialization, the bus is not
	 * initialized further.
	 */
	if (bus->dev->state & DEV_STATE_INIT_FAILED) {
		bus->state |= BUS_STATE_DEPEND_FAILED;
		goto inactivate_out;
	}

	if (bus->ops && (init = bus->ops->init[level-1])) {
		/* Note: This init1 function may register new devices */
		bus->error = init(bus);
		if (bus->error != DRVMGR_OK) {
			/* An error of some kind during bus initialization.
			 *
			 * Child devices and their buses are not inactived
			 * directly here, instead they will all be catched by
			 * do_dev_init() and do_bus_init() by checking if
			 * parent or bridge-device failed. We know that
			 * initialization will happen later for those devices.
			 */
			goto inactivate_out;
		}
	}

	DRVMGR_LOCK_WRITE();

	/* Bus taken into the new level */
	bus->level = level;

	/* Put bus into list of buses reached level 'level'.
	 * Put at end of bus list so that init[N+1]() calls comes
	 * in the same order as init[N]()
	 */
	drvmgr_list_add_tail(&mgr->buses[level], bus);

	DRVMGR_UNLOCK();

	return 0;

inactivate_out:
	DRVMGR_LOCK_WRITE();
	bus->state |= BUS_STATE_INIT_FAILED;
	bus->state |= BUS_STATE_LIST_INACTIVE;
	drvmgr_list_add_head(&mgr->buses_inactive, bus);
	DRVMGR_UNLOCK();

	DBG("do_bus_init(%d): (DEV: %s) failed\n", level, bus->dev->name);

	return 1;
}

/* Take device to initialization level 1 */
static int do_dev_init(
	struct drvmgr *mgr,
	struct drvmgr_dev *dev,
	int level)
{
	int (*init)(struct drvmgr_dev *);

	/* Try to allocate Private Device Structure for driver if driver
	 * requests for this feature.
	 */
	if (dev->drv && dev->drv->dev_priv_size && !dev->priv) {
		dev->priv = malloc(dev->drv->dev_priv_size);
		memset(dev->priv, 0, dev->drv->dev_priv_size);
	}

	/* If parent bus has failed during initialization,
	 * the device is not initialized further.
	 */
	if (dev->parent && (dev->parent->state & BUS_STATE_INIT_FAILED)) {
		dev->state |= DEV_STATE_DEPEND_FAILED;
		goto inactivate_out;
	}

	/* Call Driver's Init Routine */
	if (dev->drv && (init = dev->drv->ops->init[level-1])) {
		/* Note: This init function may register new devices */
		dev->error = init(dev);
		if (dev->error != DRVMGR_OK) {
			/* An error of some kind has occured in the
			 * driver/device, the failed device is put into the
			 * inactive list, this way Init2,3 and/or 4 will not
			 * be called for this device.
			 *
			 * The device is not removed from the bus (not
			 * unregistered). The driver can be used to find
			 * device information and debugging for example even
			 * if device initialization failed.
			 *
			 * Child buses and their devices are not inactived
			 * directly here, instead they will all be catched by
			 * do_dev_init() and do_bus_init() by checking if
			 * parent or bridge-device failed. We know that
			 * initialization will happen later for those devices.
			 */
			goto inactivate_out;
		}
	}

	DRVMGR_LOCK_WRITE();
	/* Dev taken into new level */
	dev->level = level;

	/* Put at end of device list so that init[N+1]() calls comes
	 * in the same order as init[N]()
	 */
	drvmgr_list_add_tail(&mgr->devices[level], dev);
	DRVMGR_UNLOCK();

	return 0;

inactivate_out:
	DRVMGR_LOCK_WRITE();
	dev->state |= DEV_STATE_INIT_FAILED;
	dev->state |= DEV_STATE_LIST_INACTIVE;
	drvmgr_list_add_head(&mgr->devices_inactive, dev);
	DRVMGR_UNLOCK();

	DBG("do_dev_init(%d): DRV: %s (DEV: %s) failed\n",
		level, dev->drv->name, dev->name);

	return 1; /* Failed to take device into requested level */
}

/* Register Root device driver */
int drvmgr_root_drv_register(struct drvmgr_drv *drv)
{
	struct drvmgr *mgr = &drvmgr;
	struct drvmgr_dev *root = &mgr->root_dev;

	if (mgr->root_drv) {
		/* Only possible to register root device once */
		return DRVMGR_FAIL;
	}

	/* Set root device driver */
	drv->next = NULL;
	mgr->root_drv = drv;

	/* Init root device non-NULL fields */
	root->minor_drv = -1;
	root->minor_bus = 0;
	root->businfo = mgr;
	root->name = "root bus";
	/* Custom Driver association */
	root->drv = mgr->root_drv;

	/* This registers the root device and a bus */
	drvmgr_dev_register(root);

	return DRVMGR_OK;
}

/* Register a driver */
int drvmgr_drv_register(struct drvmgr_drv *drv)
{
	struct drvmgr *mgr = &drvmgr;

	/* All drivers must have been registered before start of init, 
	 * because the manager does not scan all existing devices to find
	 * suitable hardware for this driver, and it is not protected with
	 * a lock therefore.
	 */
	if (mgr->level > 0)
		return -1;

	drv->obj_type = DRVMGR_OBJ_DRV;

	/* Put driver into list of registered drivers */
	drvmgr_list_add_head(&mgr->drivers, drv);

	/* TODO: we could scan for devices that this new driver has support
	 *       for. However, at this stage we assume that all drivers are
	 *       registered before devices are registered.
	 *
	 * LOCK: From the same assumsion locking the driver list is not needed
	 *       either.
	 */

	return 0;
}

/* Insert a device into a driver's device list and assign a driver minor number
 * to the device.
 *
 * The devices are ordered by their minor number (sorted linked list of devices)
 * the minor number is found by looking for a gap or at the end.
 */
static void drvmgr_insert_dev_into_drv(
	struct drvmgr_drv *drv,
	struct drvmgr_dev *dev)
{
	struct drvmgr_dev *curr, **pprevnext;
	int minor;

	minor = 0;
	pprevnext = &drv->dev;
	curr = drv->dev;

	while (curr) {
		if (minor < curr->minor_drv) {
			/* Found a gap. Insert new device between prev
			 * and curr. */
			break;
		}
		minor++;
		pprevnext = &curr->next_in_drv;
		curr = curr->next_in_drv;
	}
	dev->next_in_drv = curr;
	*pprevnext = dev;

	/* Set minor */
	dev->minor_drv = minor;
	drv->dev_cnt++;
}

/* Insert a device into a bus device list and assign a bus minor number to the
 * device.
 *
 * The devices are ordered by their minor number (sorted linked list of devices)
 * and by their registeration order if not using the same driver.
 *
 * The minor number is found by looking for a gap or at the end.
 */
static void drvmgr_insert_dev_into_bus(
	struct drvmgr_bus *bus,
	struct drvmgr_dev *dev)
{
	struct drvmgr_dev *curr, **pprevnext;
	int minor;

	minor = 0;
	pprevnext = &bus->children;
	curr = bus->children;

	while (curr) {
		if (dev->drv && (dev->drv == curr->drv)) {
			if (minor < curr->minor_bus) {
				/* Found a gap. Insert new device between prev
				 * and curr. */
				break;
			}
			minor++;
		}
		pprevnext = &curr->next_in_bus;
		curr = curr->next_in_bus;
	}
	dev->next_in_bus = curr;
	*pprevnext = dev;

	/* Set minor. Devices without driver are given -1 */
	if (dev->drv == NULL)
		minor = -1;
	dev->minor_bus = minor;
	bus->dev_cnt++;
}

/* Try to find a driver for a device (unite a device with driver).
 * a device with a driver
 */
static struct drvmgr_drv *drvmgr_dev_find_drv(
		struct drvmgr_dev *dev)
{
	struct drvmgr *mgr = &drvmgr;
	struct drvmgr_drv *drv;

	/* NOTE: No locking is needed here since Driver list is supposed to be
	 *       initialized once during startup, we treat it as a static
	 *       read-only list
	 */

	/* Try to find a driver that can handle this device */
	for (drv = DRV_LIST_HEAD(&mgr->drivers); drv; drv = drv->next)
		if (dev->parent->ops->unite(drv, dev) == 1)
			break;

	return drv;
}

/* Register a device */
int drvmgr_dev_register(struct drvmgr_dev *dev)
{
	struct drvmgr *mgr = &drvmgr;
	struct drvmgr_drv *drv;
	struct drvmgr_bus *bus = dev->parent;
	struct drvmgr_key *keys;
	struct drvmgr_list *init_list = &mgr->devices_inactive;

	DBG("DEV_REG: %s at bus \"%s\"\n", dev->name,
		bus && bus->dev && bus->dev->name ? bus->dev->name : "UNKNOWN");

	/* Custom driver assocation? */
	if (dev->drv) {
		drv = dev->drv;
		DBG("CUSTOM ASSOCIATION (%s to %s)\n", dev->name, drv->name);
	} else {
		/* Try to find a driver that can handle this device */
		dev->drv = drv = drvmgr_dev_find_drv(dev);
	}

	DRVMGR_LOCK_WRITE();

	/* Assign Bus Minor number and put into bus device list
	 * unless root device.
	 */
	if (bus)
		drvmgr_insert_dev_into_bus(bus, dev);

	if (!drv) {
		/* No driver found that can handle this device, put into
		 * inactive list
		 */
		dev->minor_drv = -1;
		dev->state |= DEV_STATE_LIST_INACTIVE;
	} else {
		/* United device with driver.
		 * Put the device on the registered device list
		 */
		dev->state |= DEV_STATE_UNITED;

		/* Check if user want to skip this core. This is not a
		 * normal request, however in a multi-processor system
		 * the two(or more) RTEMS instances must not use the same
		 * devices in a system, not reporting a device to
		 * it's driver will effectively accomplish this. In a
		 * non Plug & Play system one can easily avoid this
		 * problem by not report the core, but in a Plug & Play
		 * system the bus driver will report all found cores.
		 *
		 * To stop the two RTEMS instances from using the same
		 * device the user can simply define a resource entry
		 * for a certain device but set the keys field to NULL.
		 */
		if (drvmgr_keys_get(dev, &keys) == 0 && keys == NULL) {
			/* Found Driver resource entry point
			 * for this device, it was NULL, this
			 * indicates to skip the core.
			 *
			 * We put it into the inactive list
			 * marking it as ignored.
			 */
			dev->state |= DEV_STATE_IGNORED;
		} else {
			/* Assign Driver Minor number and put into driver's
			 * device list
			 */
			drvmgr_insert_dev_into_drv(drv, dev);

			/* Just register device, it will be initialized
			 * later together with bus.
			 *
			 * At the end of the list (breadth first search)
			 */
			init_list = &mgr->devices[0];

			DBG("Registered %s (DRV: %s) on %s\n",
				dev->name, drv->name,
				bus ? bus->dev->name : "NO PARENT");
		}
	}

	drvmgr_list_add_tail(init_list, dev);

	DRVMGR_UNLOCK();

	/* Trigger Device initialization if not root device and
	 * has a driver
	 */
	if (bus && dev->drv)
		drvmgr_init_update();

	return 0;
}

/* Register a bus */
int drvmgr_bus_register(struct drvmgr_bus *bus)
{
	struct drvmgr *mgr = &drvmgr;
	struct drvmgr_bus *bus_up;

	/* Get bus architecture depth - the distance from root bus */
	bus->depth = 0;
	bus_up = bus->dev->parent;
	while (bus_up) {
		bus->depth++;
		bus_up = bus_up->dev->parent;
	}

	DRVMGR_LOCK_WRITE();

	/* Put driver into list of found buses */
	drvmgr_list_add_tail(&mgr->buses[0], bus);

	DRVMGR_UNLOCK();

	/* Take bus into level1 and so on */
	drvmgr_init_update();

	return 0;
}

/* Allocate memory for a Device structure */
int drvmgr_alloc_dev(struct drvmgr_dev **pdev, int extra)
{
	struct drvmgr_dev *dev;
	int size;

	/* The extra memory "service" is aligned to 4 bytes boundary. */
	size = ((sizeof(struct drvmgr_dev) + 3) & ~0x3) + extra;
	dev = (struct drvmgr_dev *)calloc(size, 1);
	if (!dev) {
		/* Failed to allocate device structure - critical error */
		rtems_fatal_error_occurred(RTEMS_NO_MEMORY);
	}
	*pdev = dev;
	dev->obj_type = DRVMGR_OBJ_DEV;

	return 0;
}

/* Allocate memory for a Bus structure */
int drvmgr_alloc_bus(struct drvmgr_bus **pbus, int extra)
{
	struct drvmgr_bus *bus;
	int size;

	/* The extra memory "service" is aligned to 4 bytes boundary. */
	size = ((sizeof(struct drvmgr_bus) + 3) & ~0x3) + extra;
	bus = (struct drvmgr_bus *)calloc(size, 1);
	if (!bus) {
		/* Failed to allocate device structure - critical error */
		rtems_fatal_error_occurred(RTEMS_NO_MEMORY);
	}
	*pbus = bus;
	bus->obj_type = DRVMGR_OBJ_BUS;

	return 0;
}

/* Add driver resources to a bus instance */
void drvmgr_bus_res_add(struct drvmgr_bus *bus,
				struct drvmgr_bus_res *bres)
{
	/* insert first in bus resource list. Locking isn't needed since
	 * resources can only be added before resource requests are made.
	 * When bus has been registered resources are considered a read-only
	 * tree.
	 */
	bres->next = bus->reslist;
	bus->reslist = bres;
}

#ifdef RTEMS_DRVMGR_STARTUP

RTEMS_SYSINIT_ITEM(
  _DRV_Manager_initialization,
  RTEMS_SYSINIT_DRVMGR,
  RTEMS_SYSINIT_ORDER_MIDDLE
);

/* BSPs has already registered their "root bus" driver in the
 * bsp_predriver hook or so.
 *
 * Init Drivers to Level 1, constraints:
 *   - Interrupts and system clock timer does not work.
 *   - malloc() work, however other memory services may not
 *     have been initialized yet.
 *   - initializes most basic stuff
 *
 * Typical setup in Level 1:
 *   - Find most devices in system, do PCI scan and configuration.
 *   - Reset hardware if needed.
 *   - Install IRQ driver
 *   - Install Timer driver
 *   - Install console driver and debug printk()
 *   - Install extra memory.
 */
static void _DRV_Manager_init_level_1(void)
{
  _DRV_Manager_init_level(1);
}

RTEMS_SYSINIT_ITEM(
  _DRV_Manager_init_level_1,
  RTEMS_SYSINIT_DRVMGR_LEVEL_1,
  RTEMS_SYSINIT_ORDER_MIDDLE
);

/* Init Drivers to Level 2, constraints:
 *  - Interrupts can be registered and enabled.
 *  - System Clock is running
 *  - Console may be used.
 *
 * This is typically where drivers are initialized
 * for the first time.
 */
static void _DRV_Manager_init_level_2(void)
{
  _DRV_Manager_init_level(2);
}

RTEMS_SYSINIT_ITEM(
  _DRV_Manager_init_level_2,
  RTEMS_SYSINIT_DRVMGR_LEVEL_2,
  RTEMS_SYSINIT_ORDER_MIDDLE
);

/* Init Drivers to Level 3
 *
 * This is typically where normal drivers are initialized
 * for the second time, they may depend on other drivers
 * API inited in level 2
 */
static void _DRV_Manager_init_level_3(void)
{
  _DRV_Manager_init_level(3);
}

RTEMS_SYSINIT_ITEM(
  _DRV_Manager_init_level_3,
  RTEMS_SYSINIT_DRVMGR_LEVEL_3,
  RTEMS_SYSINIT_ORDER_MIDDLE
);

/* Init Drivers to Level 4,
 * Init drivers that depend on services initialized in Level 3
 */
static void _DRV_Manager_init_level_4(void)
{
  _DRV_Manager_init_level(4);
}

RTEMS_SYSINIT_ITEM(
  _DRV_Manager_init_level_4,
  RTEMS_SYSINIT_DRVMGR_LEVEL_4,
  RTEMS_SYSINIT_ORDER_MIDDLE
);

#endif /* RTEMS_DRVMGR_STARTUP */