rewrite the device subsystem interfaces (#48)
[lunaix-os.git] / lunaix-os / hal / devtree / dtm.c
diff --git a/lunaix-os/hal/devtree/dtm.c b/lunaix-os/hal/devtree/dtm.c
new file mode 100644 (file)
index 0000000..69ec2e5
--- /dev/null
@@ -0,0 +1,219 @@
+#include <hal/devtreem.h>
+#include <lunaix/device.h>
+#include <lunaix/mm/valloc.h>
+#include <lunaix/syslog.h>
+#include <lunaix/owloysius.h>
+#include <lunaix/status.h>
+
+#include <klibc/string.h>
+
+LOG_MODULE("dtm")
+
+static DECLARE_HASHTABLE(registry,    32);
+static struct device_cat* dt_category;
+
+struct figura
+{
+    char val;
+    bool optional;
+};
+
+#define hash(def)   ((unsigned int)__ptr(def))
+
+static struct dtm_driver_record*
+__locate_record(struct device_def* def)
+{
+    struct dtm_driver_record *p, *n;
+
+    hashtable_hash_foreach(registry, hash(def), p, n, node)
+    {
+        if (p->def == def) {
+            return p;
+        }
+    }
+
+    return NULL;
+}
+
+
+static inline void
+__get_patternlet(const char* str, unsigned i, size_t len, 
+                 struct figura* fig)
+{
+    fig->optional = (i + 1 < len && str[i + 1] == '?');
+
+    if (i >= len) {
+        fig->val = 0;
+        return;
+    }
+
+    fig->val = str[i];
+}
+
+/**
+ * A simplified regular expression matcher:
+ *      1. '*' matches any substring including empty string
+ *      2. '?' mark the prefixed character optional (an epsilon transition)
+ */
+static bool
+__try_match(const char* str, const char* pattern, size_t pat_sz)
+{
+    unsigned j = 0, i = 0;
+    int saved_star = -1, saved_pos = 0;
+    size_t str_sz = strlen(str);
+
+    char c;
+    struct figura p0, p1;
+
+    while (i < str_sz) {
+        c = str[i++];
+        __get_patternlet(pattern, j, pat_sz, &p0);
+        __get_patternlet(pattern, j + 1, pat_sz, &p1);
+
+        if (p0.val == c) {
+            j += 1 + !!p0.optional;
+            saved_pos = (int)i;
+            continue;
+        }
+
+        if (p0.val == '*') {
+            saved_pos = i;
+            saved_star = (int)j;
+
+            if (p1.optional || p1.val == c) {
+                ++j; --i;
+            }
+
+            continue;
+        }
+
+        if (p0.optional) {
+            --i; j += 2;
+            continue;
+        }
+
+        if (saved_star < 0) {
+            return false;
+        }
+
+        j = (unsigned)saved_star;
+        i = (unsigned)saved_pos;
+    }
+    
+    return j + 1 >= pat_sz;
+}
+
+static struct device_meta*
+__try_create_categorical(struct dt_node_base *p)
+{
+    if (!p) return NULL;
+
+    struct device_meta* parent = NULL;
+    struct device_cat* cat;
+
+    parent = __try_create_categorical(p->parent);
+    parent = parent ?: dev_meta(dt_category);
+
+    if (!p->compat.size) {
+        return parent;
+    }
+
+    if (p->binded_dev) {
+        cat = changeling_reveal(p->binded_dev, devcat_morpher);
+    }
+    else {
+        cat = device_addcat(parent, HSTR_VAL(dt_mobj(p)->name));
+        p->binded_dev = dev_mobj(cat);
+    }
+
+    return dev_meta(cat);
+}
+
+static bool
+compat_matched(struct dtm_driver_record* rec, struct dt_node_base *base)
+{
+    const char *compat;
+    struct dtm_driver_info *p, *n;
+
+    list_for_each(p, n, rec->infos.first, node)
+    {
+        size_t pat_len = strlen(p->pattern);
+        dtprop_strlst_foreach(compat, &base->compat)
+        {
+            if (__try_match(compat, p->pattern, pat_len)) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+static int
+dtm_try_create_from(struct device_def* def)
+{
+    int err;
+    const char *name;
+    struct dt_context* dtctx;
+    struct dtm_driver_record* rec;
+    struct dt_node_base *p, *n;
+    
+    dtctx = dt_main_context();
+
+    rec = __locate_record(def);
+    if (!rec) {
+        return ENOENT;
+    }
+
+    llist_for_each(p, n, &dtctx->nodes, nodes)
+    {
+        if (!p->compat.size) {
+            continue;
+        }
+
+        if (!compat_matched(rec, p)) {
+            continue;
+        }
+
+        __try_create_categorical(p);
+
+        if ((err = def->create(def, dt_mobj(p)))) {
+            name = HSTR_VAL(dt_mobj(p)->name);
+            WARN("failed to bind devtree node %s, err=%d", name, err);
+        }
+    }
+
+    return 0;
+}
+
+void
+dtm_register_entry(struct device_def* def, const char* pattern)
+{
+    struct dtm_driver_info* info;
+    struct dtm_driver_record* record;
+    
+    info = valloc(sizeof(*info));
+    info->pattern = pattern;
+
+    record = __locate_record(def);
+    if (!record) {
+        record = valloc(sizeof(*record));
+        record->def = def;
+        list_head_init(&record->infos);
+
+        hashtable_hash_in(registry, &record->node, hash(def));
+    }
+
+    list_add(&record->infos, &info->node);
+
+    device_chain_loader(def, dtm_try_create_from);
+}
+
+static void
+dtm_init()
+{
+    hashtable_init(registry);
+
+    dt_category = device_addcat(NULL, "tree");
+}
+owloysius_fetch_init(dtm_init, on_sysconf);
\ No newline at end of file