Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 100 additions & 42 deletions so3/so3/devices/device.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2017-2026 Daniel Rossier <daniel.rossier@heig-vd.ch>
* Copyright (c) 2017-2026 REDS Institute, HEIG-VD
* Author: Daniel Rossier <daniel.rossier@heig-vd.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
Expand All @@ -23,6 +24,7 @@
#include <ctype.h>
#include <vfs.h>
#include <log.h>
#include <hashmap.h>

#include <asm/setup.h>

Expand Down Expand Up @@ -99,6 +101,15 @@ bool fdt_device_is_available(void *fdt_addr, int node_offset)
return false;
}

/*
* Descriptor stored in the driver hash map: it ties a compatible string (the
* map key) to the matching driver's init function and its initcall level.
*/
struct driver_match {
int (*init)(dev_t *dev, int fdt_offset);
int level;
};

/*
* Read the content of a device tree and associate a generic device info structure to each
* relevant entry.
Expand All @@ -111,11 +122,13 @@ void parse_dtb(void *fdt_addr)
{
unsigned int drivers_count[INITCALLS_LEVELS];
driver_initcall_t *driver_entries[INITCALLS_LEVELS];
dev_t *dev;
int i, level;
int ret;
struct list_head pending[INITCALLS_LEVELS];
struct driver_match *matches, *match;
struct hashmap *driver_map;
dev_t *dev, *tmp;
int i, level, ret, m;
int offset, new_off;
bool found;
int total_drivers;

drivers_count[CORE] = ll_entry_count(driver_initcall_t, core);
driver_entries[CORE] = ll_entry_start(driver_initcall_t, core);
Expand All @@ -127,51 +140,96 @@ void parse_dtb(void *fdt_addr)
LOG_DEBUG("%s: # entries for postcore drivers : %d\n", __func__, drivers_count[POSTCORE]);
LOG_DEBUG("Now scanning the device tree to retrieve all devices...\n");

for (level = 0; level < INITCALLS_LEVELS; level++) {
total_drivers = drivers_count[CORE] + drivers_count[POSTCORE];

/*
* Index every registered driver by its compatible string so that each
* device tree node can be matched in O(1). Building the map is O(M) over
* the M drivers and the single device tree scan below is O(N) over the N
* nodes, turning the former O(N * M) nested loop into an O(N + M) parsing.
*/
driver_map = hashmap_create(total_drivers);
ASSERT(driver_map != NULL);

matches = total_drivers ? (struct driver_match *) malloc(total_drivers * sizeof(*matches)) : NULL;
ASSERT(total_drivers == 0 || matches != NULL);

for (level = 0; level < INITCALLS_LEVELS; level++)
INIT_LIST_HEAD(&pending[level]);

/*
* Insert postcore drivers before core ones: hashmap_put() overwrites on a
* duplicate key, so a compatible shared by both levels ends up mapped to
* its core driver, preserving the core-before-postcore precedence.
*/
m = 0;
for (level = INITCALLS_LEVELS - 1; level >= 0; level--) {
for (i = 0; i < drivers_count[level]; i++) {
matches[m].init = driver_entries[level][i].init;
matches[m].level = level;
hashmap_put(driver_map, driver_entries[level][i].compatible, &matches[m]);
m++;
}
}

/*
* Scan the device tree once. A single scratch entry probes every node --
* get_dev_info() resets it on each call -- and is only kept (and replaced
* by a fresh allocation) when the node matches a driver. Matched nodes are
* queued on the list of their initcall level so that the initialization
* below can honour the core-before-postcore ordering.
*/
dev = (dev_t *) malloc(sizeof(dev_t));
ASSERT(dev != NULL);

offset = 0;
while ((new_off = get_dev_info(fdt_addr, offset, "*", dev)) != -1) {
offset = new_off;

if (!fdt_device_is_available(fdt_addr, new_off))
continue;

match = (struct driver_match *) hashmap_get(driver_map, dev->compatible);
if (!match)
continue;

list_add_tail(&dev->list, &pending[match->level]);

dev = (dev_t *) malloc(sizeof(dev_t));
ASSERT(dev != NULL);
memset(dev, 0, sizeof(dev_t));

found = false;
offset = 0;

while ((new_off = get_dev_info(fdt_addr, offset, "*", dev)) != -1) {
if (fdt_device_is_available(fdt_addr, new_off)) {
for (i = 0; i < drivers_count[level]; i++) {
if (!strcmp(dev->compatible, driver_entries[level][i].compatible)) {
found = true;

LOG_DEBUG("Found compatible: %s\n", driver_entries[level][i].compatible);
LOG_DEBUG(" Compatible: %s\n", dev->compatible);
LOG_DEBUG(" Status: %s\n", dev_state_str(dev->status));
LOG_DEBUG(" Initcall level: %d\n", level);

if (dev->status == STATUS_INIT_PENDING) {
ret = driver_entries[level][i].init(dev, new_off);
BUG_ON(ret);

dev->status = STATUS_INITIALIZED;
list_add_tail(&dev->list, &devices);
}
break;
}
}
}
if (!found)
free(dev);
}

/* The last scratch buffer was never consumed. */
free(dev);

/*
* Initialize the matched devices, core drivers before postcore ones and in
* device tree order within each level, then move them to the global list.
*/
for (level = 0; level < INITCALLS_LEVELS; level++) {
list_for_each_entry_safe(dev, tmp, &pending[level], list) {
match = (struct driver_match *) hashmap_get(driver_map, dev->compatible);

offset = new_off;
LOG_DEBUG("Found compatible: %s\n", dev->compatible);
LOG_DEBUG(" Status: %s\n", dev_state_str(dev->status));
LOG_DEBUG(" Initcall level: %d\n", level);

dev = (dev_t *) malloc(sizeof(dev_t));
ASSERT(dev != NULL);
memset(dev, 0, sizeof(dev_t));
if (dev->status == STATUS_INIT_PENDING) {
ret = match->init(dev, dev->offset_dts);
BUG_ON(ret);

found = false;
dev->status = STATUS_INITIALIZED;
}

list_del(&dev->list);
list_add_tail(&dev->list, &devices);
}
}

/* We have always the last allocation which will not be used */
free(dev);
if (matches)
free(matches);

hashmap_free(driver_map);
}

/* Register a device. Usually called from the device driver. */
Expand Down
55 changes: 55 additions & 0 deletions so3/so3/include/hashmap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2017-2026 REDS Institute, HEIG-VD
* Author: Daniel Rossier <daniel.rossier@heig-vd.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/

#ifndef HASHMAP_H
#define HASHMAP_H

#include <types.h>

/*
* A generic hash map associating string keys to void * values.
*
* It uses open addressing with linear probing and grows automatically to keep
* the load factor -- and therefore the lookup cost -- bounded. Keys are copied
* and owned by the map; values are owned by the caller and left untouched.
*/
struct hashmap;

/*
* Create a map pre-sized to hold at least `capacity_hint` keys without growing.
* Returns NULL on allocation failure.
*/
struct hashmap *hashmap_create(size_t capacity_hint);

/* Release the map and its copied keys. Values are not freed. */
void hashmap_free(struct hashmap *map);

/*
* Associate `value` with `key`, replacing any value previously stored for that
* key. The key string is copied. Returns 0 on success, -1 on allocation failure.
*/
int hashmap_put(struct hashmap *map, const char *key, void *value);

/* Return the value associated with `key`, or NULL if the key is absent. */
void *hashmap_get(const struct hashmap *map, const char *key);

/* Return the number of entries currently stored in the map. */
size_t hashmap_count(const struct hashmap *map);

#endif /* HASHMAP_H */
39 changes: 39 additions & 0 deletions so3/so3/include/math.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2017-2026 REDS Institute, HEIG-VD
* Author: Daniel Rossier <daniel.rossier@heig-vd.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/

#ifndef MATH_H
#define MATH_H

#include <types.h>

/*
* Round @n up to the next power of two. Returns @n unchanged when it already is
* a power of two, and 1 for @n == 0.
*/
static inline size_t round_up_pow2(size_t n)
{
size_t p = 1;

while (p < n)
p <<= 1;

return p;
}

#endif /* MATH_H */
1 change: 1 addition & 0 deletions so3/so3/lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ lib-y += ctype.o
lib-y += vsprintf.o printk.o
lib-y += strtod.o
lib-y += list_sort.o
lib-y += hashmap.o

Loading
Loading