Merge remote-tracking branch 'origin/master' into isa/arm64
[lunaix-os.git] / lunaix-os / hal / devtree / dt.c
1 #include <lunaix/mm/valloc.h>
2 #include <lunaix/syslog.h>
3 #include <lunaix/owloysius.h>
4
5 #include "devtree.h"
6
7 LOG_MODULE("dtb")
8
9 static morph_t* devtree_obj_root;
10 static struct dt_context dtctx;
11
12 void
13 fdt_load(struct fdt_blob* fdt, ptr_t base)
14 {
15     struct fdt_header* hdr;
16
17     fdt->fdt_base = base;
18
19     hdr = fdt->header;
20     if (hdr->magic != FDT_MAGIC) {
21         FATAL("invalid dtb, unexpected magic: 0x%x, expect: 0x%x", 
22                 hdr->magic, FDT_MAGIC);
23     }
24
25     fdt->plat_rsvd_base = base + hdr->off_mem_rsvmap;
26     fdt->str_block_base = base + hdr->off_dt_strings;
27     fdt->root.ptr       = base + hdr->off_dt_struct;
28 }
29
30 bool
31 fdt_next_boot_rsvdmem(struct fdt_blob* fdt, fdt_loc_t* loc, 
32                       struct dt_memory_node* mem)
33 {
34     fdt_loc_t current;
35
36     current = *loc;
37
38     if (!current.rsvd_ent->addr && !current.rsvd_ent->addr) {
39         return false;
40     }
41
42     mem->base = current.rsvd_ent->addr;
43     mem->size = current.rsvd_ent->size;
44     mem->type = FDT_MEM_RSVD;
45
46     current.rsvd_ent++;
47     *loc = current;
48
49     return true;
50 }
51
52 fdt_loc_t
53 fdt_next_token(fdt_loc_t loc, int* delta_depth)
54 {
55     int d = 0;
56
57     do {
58         if (fdt_node(loc.token)) {
59             d++;
60             loc.ptr += strlen(loc.node->name) + 1;
61             loc.ptr  = ROUNDUP(loc.ptr, sizeof(int));
62         }
63         else if (fdt_node_end(loc.token)) {
64             d--;
65         }
66         else if (fdt_prop(loc.token)) {
67             loc.ptr   += loc.prop->len + 2 * sizeof(int);
68             loc.ptr    = ROUNDUP(loc.ptr, sizeof(int));
69         }
70
71         loc.token++;
72     } while (fdt_nope(loc.token));
73
74     *delta_depth = d;
75     return loc;
76 }
77
78 bool
79 fdt_next_sibling(fdt_loc_t loc, fdt_loc_t* loc_out)
80 {
81     int depth = 0, new_depth = 0;
82
83     do {
84         loc = fdt_next_token(loc, &new_depth);
85         depth += new_depth;
86     } while (depth > 0);
87
88     *loc_out = loc;
89     return !fdt_node_end(loc.token);
90 }
91
92 fdt_loc_t
93 fdt_descend_into(fdt_loc_t loc) 
94 {
95     fdt_loc_t new_loc;
96     int depth = 0;
97
98     new_loc = fdt_next_token(loc, &depth);
99
100     return depth != 1 ? loc : new_loc;
101 }
102
103 bool
104 fdt_find_prop(const struct fdt_blob* fdt, fdt_loc_t loc, 
105               const char* name, struct dtp_val* val)
106 {
107     char* prop_name;
108
109     loc = fdt_descend_into(loc);
110
111     do
112     {
113         if (!fdt_prop(loc.token)) {
114             continue;
115         }
116
117         prop_name = fdt_prop_key(fdt, loc);
118         
119         if (!streq(prop_name, name)) {
120             continue;
121         }
122         
123         if (likely(val)) {
124             val->encoded = (dt_enc_t)__prop_val_ptr(loc.prop);
125             val->size    = loc.prop->len;
126         }
127         return true;
128         
129     } while (fdt_next_sibling(loc, &loc));
130
131     return false;
132 }
133
134 bool
135 fdt_memscan_begin(struct fdt_memscan* mscan, const struct fdt_blob* fdt)
136 {
137     struct dtp_val val;
138     fdt_loc_t loc;
139
140     loc  = fdt->root;
141     loc  = fdt_descend_into(loc);
142
143     if (fdt_find_prop(fdt, loc, "#address-cells", &val))
144     {
145         mscan->root_addr_c = val.ref->u32_val;
146     }
147
148     if (fdt_find_prop(fdt, loc, "#size-cells", &val))
149     {
150         mscan->root_size_c = val.ref->u32_val;
151     }
152
153     mscan->loc = loc;
154     mscan->node_type = FDT_MEM_FREE;
155
156     return true;
157 }
158
159 #define get_size(mscan, val)    \
160     (mscan->root_size_c == 1 ? (val)->ref->u32_val : (val)->ref->u64_val)
161
162 #define get_addr(mscan, val)    \
163     (mscan->root_addr_c == 1 ? (val)->ref->u32_val : (val)->ref->u64_val)
164
165 bool
166 fdt_memscan_nextnode(struct fdt_memscan* mscan, struct fdt_blob* fdt)
167 {
168     char* prop_name;
169
170     struct dtp_val val, reg_val;
171     fdt_loc_t loc, next;
172     struct dtpropi dtpi;
173     bool has_reg = false, found = false;
174
175     next = mscan->loc;
176
177 restart:
178     do
179     {
180         loc = next;
181         
182         if (!fdt_node(loc.token))
183             continue;
184
185         if (mscan->node_type != FDT_MEM_FREE) {
186             found = true;
187             continue;
188         }
189
190         if (streq(loc.node->name, "reserved-memory")) {
191             // dived into /reserved-memory, walking for childrens
192             mscan->node_type = FDT_MEM_RSVD;
193             loc = fdt_descend_into(loc);
194             continue;
195         }
196
197         if (!fdt_find_prop(fdt, loc, "device_type", &val))
198             continue;
199
200         if (!streq(val.str_val, "memory"))
201             continue;
202
203         found = true;
204     } while (fdt_next_sibling(loc, &next) && !found);
205
206     if (found) goto _found;
207
208     // emerged from /reserved-memory, resume walking for /memory
209     if (mscan->node_type != FDT_MEM_FREE) {
210         mscan->node_type = FDT_MEM_FREE;
211         goto restart;
212     }
213
214     return false;
215
216 _found:
217
218     dtpi_init_empty(&mscan->regit);
219     mscan->found = loc;
220     mscan->loc   = next;
221
222     has_reg = fdt_find_prop(fdt, loc, "reg", &val);
223     if (mscan->node_type == FDT_MEM_RSVD) {
224         goto do_rsvd_child;
225     }
226
227     if (!has_reg)
228     {
229         WARN("malformed memory node");
230         goto restart;
231     }
232
233     dtpi_init(&mscan->regit, &val);
234
235     return true;
236
237 do_rsvd_child:
238
239     mscan->node_attr.nomap = fdt_find_prop(fdt, loc, "no-map", NULL);
240     mscan->node_attr.reusable = fdt_find_prop(fdt, loc, "reusable", NULL);
241
242     if (has_reg)
243     {
244         dtpi_init(&mscan->regit, &val);
245         mscan->node_type = FDT_MEM_RSVD;
246         return true;
247     }
248     
249     if (!fdt_find_prop(fdt, loc, "size", &val)) 
250     {
251         WARN("malformed reserved memory child node");
252         goto restart;
253     }
254     
255     mscan->node_type = FDT_MEM_RSVD_DYNAMIC;
256     mscan->node_attr.total_size = get_size(mscan, &val);
257
258     if (fdt_find_prop(fdt, loc, "alignment", &val)) {
259         mscan->node_attr.alignment = get_size(mscan, &val);
260     }
261
262     if (fdt_find_prop(fdt, loc, "alloc-ranges", &val)) {
263         dtpi_init(&mscan->regit, &val);
264     }
265
266     return true;
267 }
268
269 bool
270 fdt_memscan_nextrange(struct fdt_memscan* mscan, struct dt_memory_node* mem)
271 {
272     struct dtp_val val;
273
274     if (dtpi_is_empty(&mscan->regit)) {
275         return false;
276     }
277
278     if (!dtpi_has_next(&mscan->regit)) {
279         return false;
280     }
281
282     if (dtpi_next_val(&mscan->regit, &val, mscan->root_addr_c)) {
283         mem->base = get_addr(mscan, &val);
284     }
285
286     if (dtpi_next_val(&mscan->regit, &val, mscan->root_size_c)) {
287         mem->size = get_size(mscan, &val);
288     }
289
290     mem->type = mscan->node_type;
291     
292     if (mem->type == FDT_MEM_RSVD_DYNAMIC) {
293         mem->dyn_alloc_attr = mscan->node_attr;
294     }
295
296     return true;
297 }
298
299 static bool
300 __parse_stdbase_prop(struct fdt_blob* fdt, fdt_loc_t loc, 
301                      struct dtn_base* node)
302 {
303     if (propeq(fdt, loc, "compatible")) {
304         __mkprop_ptr(loc, &node->compat);
305     } 
306
307     else if (propeq(fdt, loc, "phandle")) {
308         node->phandle = __prop_getu32(loc);
309     }
310     
311     else if (propeq(fdt, loc, "#address-cells")) {
312         node->addr_c = (char)__prop_getu32(loc);
313     } 
314     
315     else if (propeq(fdt, loc, "#size-cells")) {
316         node->sz_c = (char)__prop_getu32(loc);
317     } 
318     
319     else if (propeq(fdt, loc, "#interrupt-cells")) {
320         node->intr_c = (char)__prop_getu32(loc);
321     } 
322     
323     else if (propeq(fdt, loc, "status")) {
324         char peek = loc.prop->val_str[0];
325         if (peek == 'o') {
326             node->status = STATUS_OK;
327         }
328         else if (peek == 'r') {
329             node->status = STATUS_RSVD;
330         }
331         else if (peek == 'd') {
332             node->status = STATUS_DISABLE;
333         }
334         else if (peek == 'f') {
335             node->status = STATUS_FAIL;
336         }
337     }
338
339     else {
340         return false;
341     }
342
343     return true;
344 }
345
346 static bool
347 __parse_stdnode_prop(struct fdt_blob* fdt, fdt_loc_t loc, struct dtn* node)
348 {
349     if (propeq(fdt, loc, "reg")) {
350         __mkprop_ptr(loc, &node->reg);
351     }
352
353     else if (propeq(fdt, loc, "ranges")) {
354         __mkprop_ptr(loc, &node->ranges);
355     }
356
357     else if (propeq(fdt, loc, "dma-ranges")) {
358         __mkprop_ptr(loc, &node->dma_ranges);
359     }
360
361     else {
362         return false;
363     }
364
365     return true;
366 }
367
368 static bool
369 __parse_stdflags(struct fdt_blob* fdt, fdt_loc_t loc, struct dtn_base* node)
370 {
371     if (propeq(fdt, loc, "dma-coherent")) {
372         node->dma_coherent = true;
373     }
374
375     else if (propeq(fdt, loc, "dma-noncoherent")) {
376         node->dma_ncoherent = true;
377     }
378
379     else if (propeq(fdt, loc, "interrupt-controller")) {
380         node->intr_controll = true;
381     }
382
383     else {
384         return false;
385     }
386
387     return true;
388 }
389
390 static inline void
391 __dt_node_set_name(struct dtn* node, const char* name)
392 {
393     changeling_setname(&node->mobj, name);
394 }
395
396 static inline void
397 __init_prop_table(struct dtn_base* node)
398 {
399     struct dtp_table* propt;
400
401     propt = valloc(sizeof(*propt));
402     hashtable_init(propt->_op_bucket);
403
404     node->props = propt;
405 }
406
407 #define prop_table_add(node, prop)                                             \
408             hashtable_hash_in( (node)->props->_op_bucket,                      \
409                               &(prop)->ht, (prop)->key.hash);
410
411 static void
412 __parse_other_prop(struct fdt_blob* fdt, fdt_loc_t loc, struct dtn_base* node)
413 {
414     struct dtp* prop;
415     const char* key;
416     unsigned int hash;
417
418     prop = valloc(sizeof(*prop));
419     key  = fdt_prop_key(fdt, loc);
420
421     prop->key = HSTR(key, strlen(key));
422     __mkprop_ptr(loc, &prop->val);
423
424     hstr_rehash(&prop->key, HSTR_FULL_HASH);
425
426     prop_table_add(node, prop);
427 }
428
429 static void
430 __fill_node(struct fdt_blob* fdt, fdt_loc_t loc, struct dtn* node)
431 {
432     if (__parse_stdflags(fdt, loc, &node->base)) {
433         return;
434     }
435
436     if (__parse_stdbase_prop(fdt, loc, &node->base)) {
437         return;
438     }
439
440     if (__parse_stdnode_prop(fdt, loc, node)) {
441         return;
442     }
443
444     if (parse_stdintr_prop(fdt, loc, &node->intr)) {
445         return;
446     }
447
448     __parse_other_prop(fdt, loc, &node->base);
449 }
450
451 static inline void
452 __set_parent(struct dtn_base* parent, struct dtn_base* node)
453 {
454     morph_t* parent_obj;
455     
456     parent_obj   = devtree_obj_root;
457     node->parent = parent;
458     
459     if (parent) {
460         node->addr_c = parent->addr_c;
461         node->sz_c = parent->sz_c;
462         node->intr_c = parent->intr_c;
463         parent_obj = dt_mobj(parent);
464     }
465
466     changeling_attach(parent_obj, dt_mobj(node));
467 }
468
469 static inline void
470 __init_node_regular(struct dtn* node)
471 {
472     __init_prop_table(&node->base);
473     changeling_morph_anon(NULL, node->mobj, dt_morpher);
474  
475     node->intr.parent_hnd = PHND_NULL;
476 }
477
478 static void
479 __expand_extended_intr(struct dtn_intr* intrupt)
480 {
481     struct dtpropi it;
482     struct dtp_val  arr;
483     struct dtn *domain;
484     struct dtspec_intr* ispec;
485     int nr_intrs = 0;
486
487     if (!intrupt->extended) {
488         nr_intrs  = intrupt->raw_ispecs.size / sizeof(u32_t);
489         nr_intrs /= intrupt->parent->base.intr_c; 
490         goto done;
491     }
492
493     arr = intrupt->raw_ispecs;
494
495     llist_init_head(&intrupt->ext_ispecs);
496     
497     dtpi_init(&it, &arr);
498
499     while(dtpi_has_next(&it)) 
500     {
501         domain = dtpi_next_hnd(&it);
502
503         if (!domain) {
504             WARN("(intr_extended) malformed phandle");
505             continue;
506         }
507
508         ispec = valloc(sizeof(*ispec));
509         
510         ispec->domain = domain;
511         dtpi_next_val(&it, &ispec->val, domain->base.intr_c);
512
513         llist_append(&intrupt->ext_ispecs, &ispec->ispecs);
514         nr_intrs++;
515     };
516
517 done:
518     intrupt->nr_intrs = nr_intrs;
519 }
520
521 static void
522 __resolve_phnd_references()
523 {
524     struct dtn_base *pos, *n;
525     struct dtn *node, *parent, *default_parent;
526     struct dtn_intr* intrupt;
527     dt_phnd_t phnd;
528     
529     llist_for_each(pos, n, &dtctx.nodes, nodes)
530     {
531         node = dtn_from(pos);
532         intrupt = &node->intr;
533
534         if (intrupt->parent_hnd == PHND_NULL) {
535             continue;
536         }
537
538         phnd = intrupt->parent_hnd;
539         default_parent = (struct dtn*)node->base.parent;
540         parent = default_parent;
541
542         if (phnd != PHND_NULL) {
543             parent = dt_resolve_phandle(phnd);
544         }
545
546         if (!parent) {
547             WARN("dtb: (phnd_resolve) malformed phandle: %d", phnd);
548             parent = default_parent;
549         }
550
551         intrupt->parent = parent;
552
553         __expand_extended_intr(intrupt);
554     }
555 }
556
557 static void
558 __resolve_inter_map()
559 {
560     struct dtn_base *pos, *n;
561
562     llist_for_each(pos, n, &dtctx.nodes, nodes)
563     {
564         dt_resolve_interrupt_map(dtn_from(pos));
565     }
566 }
567
568 bool
569 dt_load(ptr_t dtb_dropoff)
570 {
571     llist_init_head(&dtctx.nodes);
572     hashtable_init(dtctx.phnds_table);
573
574     struct fdt_blob *fdt;
575     struct dtn      *node,
576                     *stack[16] = { NULL };
577     
578     int depth = 0, delta = 0, nr_nodes = 0;
579     fdt_loc_t  loc, next_loc;
580
581     fdt = &dtctx.fdt;
582     fdt_load(&dtctx.fdt, dtb_dropoff);
583
584     loc = fdt->root;
585     
586     while (!fdt_eof(loc.token)) 
587     {
588         next_loc = fdt_next_token(loc, &delta);
589
590         if (depth >= 16) {
591             // tree too deep
592             ERROR("strange dtb, too deep to dive.");
593             return false;
594         }
595
596         assert(depth >= 0);
597         node = stack[depth];
598
599         if (fdt_node(loc.token))
600         {
601             assert(!node);
602
603             node = vzalloc(sizeof(struct dtn));
604             __init_node_regular(node);
605             llist_append(&dtctx.nodes, &node->base.nodes);
606
607             __dt_node_set_name(node, loc.node->name);
608
609             if (depth) {
610                 __set_parent(&stack[depth - 1]->base, &node->base);
611             }
612
613             nr_nodes++;
614             stack[depth] = node;
615         }
616
617         else if (depth > 1 && fdt_node_end(loc.token))
618         {
619             stack[depth - 1] = NULL;
620         }
621
622         else if (fdt_prop(loc.token))
623         {
624             node = stack[depth - 1];
625
626             assert(depth && node);
627             __fill_node(fdt, loc, node);
628         }
629
630         depth += delta;
631         loc = next_loc;
632     }
633
634     dtctx.root = stack[0];
635
636     __resolve_phnd_references();
637     __resolve_inter_map();
638
639     INFO("%d nodes loaded.", nr_nodes);
640
641     return true;
642 }
643
644 struct dtn*
645 dt_resolve_phandle(dt_phnd_t phandle)
646 {
647     struct dtn_base *pos, *n;
648     llist_for_each(pos, n, &dtctx.nodes, nodes)
649     {
650         if (pos->phandle == phandle) {
651             return (struct dtn*)pos;
652         }
653     }
654
655     return NULL;
656 }
657
658 static bool
659 __byname_predicate(struct dtn_iter* iter, struct dtn_base* node)
660 {
661     int i = 0;
662     const char* be_matched = HSTR_VAL(node->mobj.name);
663     const char* name = (const char*)iter->closure;
664
665     while (be_matched[i] && name[i])
666     {
667         if (be_matched[i] != name[i]) {
668             return false;
669         }
670
671         i++;
672     }
673
674     return true;
675 }
676
677 void
678 dt_begin_find_byname(struct dtn_iter* iter, 
679               struct dtn* node, const char* name)
680 {
681     dt_begin_find(iter, node, __byname_predicate, name);
682 }
683
684 void
685 dt_begin_find(struct dtn_iter* iter, struct dtn* node, 
686               node_predicate_t pred, void* closure)
687 {
688     node = node ? : (struct dtn*)dtctx.root;
689
690     iter->head = &node->base;
691     iter->matched = NULL;
692     iter->closure = closure;
693     iter->pred = pred;
694
695     morph_t *pos, *n;
696     struct dtn_base* base;
697     changeling_for_each(pos, n, &node->mobj)
698     {
699         base = &changeling_reveal(pos, dt_morpher)->base;
700         if (pred(iter, base)) {
701             iter->matched = base;
702             break;
703         }
704     }
705 }
706
707 bool
708 dt_find_next(struct dtn_iter* iter,
709              struct dtn_base** matched)
710 {
711     if (!dt_found_any(iter)) {
712         return false;
713     }
714
715     struct dtn *node;
716     morph_t *pos, *head;
717
718     head = dt_mobj(iter->head);
719     pos  = dt_mobj(iter->matched);
720     *matched = iter->matched;
721
722     while (&pos->sibs != &head->subs)
723     {
724         pos = list_next(pos, morph_t, sibs);
725         node = changeling_reveal(pos, dt_morpher);
726
727         if (!iter->pred(iter, &node->base)) {
728             continue;
729         }
730
731         iter->matched = &node->base;
732         return true;
733     }
734
735     return false;
736 }
737
738 struct dtp_val*
739 dt_getprop(struct dtn_base* base, const char* name)
740 {
741     struct hstr hashed_name;
742     struct dtp *pos, *n;
743     unsigned int hash;
744
745     hashed_name = HSTR(name, strlen(name));
746     hstr_rehash(&hashed_name, HSTR_FULL_HASH);
747     hash = hashed_name.hash;
748
749     hashtable_hash_foreach(base->props->_op_bucket, hash, pos, n, ht)
750     {
751         if (HSTR_EQ(&pos->key, &hashed_name)) {
752             return &pos->val;
753         }
754     }
755
756     return NULL;
757 }
758
759 void
760 dtpx_compile_proplet(struct dtprop_def* proplet)
761 {
762     int i;
763     unsigned int acc = 0;
764     struct dtprop_def* pl;
765     
766     for (i = 0; proplet[i].type && i < 10; ++i)
767     {
768         pl = &proplet[i];
769
770         if (pl->type == DTP_COMPX) {
771             if (pl->cell == 1) 
772                 pl->type = DTP_U32;
773             else if (pl->cell == 2)
774                 pl->type = DTP_U64;
775         }
776
777         pl->acc_sz = acc;
778         acc += pl->cell;
779     }
780
781     if (proplet[i - 1].type && i == 10) {
782         FATAL("invalid proplet: no terminator detected");
783     }
784
785     proplet[i].acc_sz = acc;
786 }
787
788 void
789 dtpx_prepare_with(struct dtpropx* propx, struct dtp_val* prop,
790                   struct dtprop_def* proplet)
791 {
792     int i;
793     bool has_str = false;
794     
795     for (i = 0; proplet[i].type; ++i);
796
797     propx->proplet = proplet;
798     propx->proplet_len = i;
799     propx->proplet_sz = proplet[i].acc_sz;
800     propx->raw = prop;
801     propx->row_loc = 0;
802 }
803
804 bool
805 dtpx_goto_row(struct dtpropx* propx, int row)
806 {
807     off_t loc;
808
809     loc  = propx->proplet_sz;
810     loc *= row;
811
812     if (loc * sizeof(u32_t) >= propx->raw->size) {
813         return false;
814     }
815
816     propx->row_loc = loc;
817     return true;
818 }
819
820 bool
821 dtpx_next_row(struct dtpropx* propx)
822 {
823     off_t loc;
824
825     loc  = propx->row_loc;
826     loc += propx->proplet_sz;
827
828     if (loc * sizeof(u32_t) >= propx->raw->size) {
829         return false;
830     }
831
832     propx->row_loc = loc;
833     return true;
834 }
835
836 bool
837 dtpx_extract_at(struct dtpropx* propx, 
838                 struct dtprop_xval* val, int col)
839 {
840     struct dtprop_def* def;
841     union dtp_baseval* raw;
842     dt_enc_t enc;
843
844     if (unlikely(col >= propx->proplet_len)) {
845         return false;
846     }
847
848     def = &propx->proplet[col];
849     enc = &propx->raw->encoded[propx->row_loc + def->acc_sz];
850     raw = (union dtp_baseval*)enc;
851
852     val->archetype = def;
853
854     switch (def->type)
855     {
856         case DTP_U32:
857             val->u32 = raw->u32_val;
858             break;
859
860         case DTP_U64:
861             val->u64 = raw->u64_val;
862             break;
863
864         case DTP_PHANDLE:
865         {
866             ptr_t hnd = raw->phandle;
867             val->phandle = dt_resolve_phandle(hnd);
868         } break;
869
870         case DTP_COMPX:
871             {
872                 val->composite = enc;
873             }
874             break;
875         
876         default:
877             break;
878     }
879
880     return true;
881 }
882
883 bool
884 dtpx_extract_loc(struct dtpropx* propx, 
885                  struct dtprop_xval* val, int row, int col)
886 {
887     ptr_t loc = propx->row_loc;
888
889     if (!dtpx_goto_row(propx, row))
890         return false;
891
892     
893     bool r = dtpx_extract_at(propx, val, col);
894     propx->row_loc = loc;
895     return r;
896 }
897
898 bool
899 dtpx_extract_row(struct dtpropx* propx, struct dtprop_xval* vals, int len)
900 {
901     assert(len == propx->proplet_len);
902
903     for (int i = 0; i < len; i++)
904     {
905         if (!dtpx_extract_at(propx, &vals[i], i)) {
906             return false;
907         }
908     }
909     
910     return true;
911 }
912
913 struct dt_context*
914 dt_main_context()
915 {
916     return &dtctx;
917 }
918
919 static void
920 __init_devtree()
921 {
922     devtree_obj_root = changeling_spawn(NULL, NULL);
923 }
924 owloysius_fetch_init(__init_devtree, on_sysconf);