Closed LuoZhongYao closed 1 month ago
Not sure if there's a need for this, and I think zbus
can be configured to be light-weight too? @rodrigopex
@pdgendt The lightweight zbus is already there, so use listeners and disable all the unnecessary features.
There are many scenarios where zbus is too heavyweight and cumbersome to use, and callback function registration is highly coupled.
@LuoZhongYao, could you please give us some examples of the problem? If possible, add them to the issue description.
There are many scenarios where zbus is too heavyweight and cumbersome to use, and callback function registration is highly coupled.
@LuoZhongYao, could you please give us some examples of the problem? If possible, add them to the issue description.
I compared them carefully, and zbus's LISTENER is very similar to ebus. zbus is based on channels, and ebus is based on events. Maybe ebus is not needed.
Introduction
Ebus is a static event publish-subscribe bus, where publishers and subscribers have a many-to-many relationship. Compared to zbus, ebus is lighter.
Problem description
There are many scenarios where zbus is too heavyweight and cumbersome to use, and callback function registration is highly coupled.
Proposed change
Detailed RFC
Use
uint32_t id
to identify events,id
is generated byuint16_t namepsace
anduint16_t event
,ebus_id = namespace << 16 | event
. Thenamespace
defines the namespace, the module registers the namespace usingEBUS_NAMESPACE_REGISTER
(e.gEBUS_NAMESPACE_REGISTER(power)
, registers thepower
namespace), and the module defines the eventevent
in its own namespace as needed.. The user needs to declare the namespace using
EBUS_NAMESPACE_DECLARE, (e.g
EBUS_NAMESPACE_DECLARE(power), declare the
powernamespace). Then use
EBUS_SUBSCRIBE(namespace, ev, callabck, userdata), to listen for
evevents from
namespace, and when the publisher publishes
evevents from
namespaceusing
EBUS_PUBLISH(namespace, ev), the subscriber calls
EBUS_PUBLISH(namespace, ev). event, the subscriber calls
callback. Since
ebus_idis a constant, a perfect hash table can be generated using
gperf` to speed up the process. The initial design is as follows.pragma once
if !defined(CONFIG_EBUS)
define EBUS_NAMESPACE_REGISTER(name)
define EBUS_NAMESPACE_DECLARE(name)
define EBUS_PUBLISH(_namespace, _ev, ...)
define EBUS_SUBSCRIBE(_namespace, _ev, _callback, _userdata)
else / CONFIG_EBUS /
include
include
define ebustext attribute((section__(".TEXT.ebus")))
typedef void (ebus_subscriber_t)(uint32_t ebus, void userdata, void *appendix);
struct ebus_subscriber { void *userdata; ebus_subscriber_t subscriber; };
struct ebus_slot_subscriber { uintptr_t ev; const uint8_t namespace; const struct ebus_subscriber subscriber; } ;
struct ebus_publish { void appendix; void (drop)(struct ebus_publish *publish); };
extern uint8_t ebus_namespace_start[]; extern uint8_t __ebus_namespace_end[]; extern const struct ebus_subscriber ebus_subscriber_start[]; extern const struct ebus_subscriber __ebus_subscriber_end[]; extern int ebus_publish(uint32_t ebus, struct ebus_publish *publish);
define EBUS_ORIGINAL(_namespace, _ev) (((uint32_t)(&EBUS_NAMESPACE(_namespace)) << 16) | (_ev))
define EBUS_NAMESPACE(_name) UTIL_CAT(__ebusnamespace, _name)
define EBUS_NAMESPACE_REGISTER(_name) \
define EBUS_NAMESPACE_DECLARE(_name) extern const uint8_t EBUS_NAMESPACE(_name)
define EBUS_PUBLISH(_namespace, _ev, ...) \
define _EBUS_SUBSCRIBE(_namespace, _ev, _subscriber, _userdata, _idx) \
define EBUS_SUBSCRIBE(_namespace, _ev, _subscriber, _userdata) \
endif / CONFIG_EBUS /
SPDX-License-Identifier: Apache-2.0
import sys import argparse import os import struct import pickle from packaging import version
import elftools from elftools.elf.elffile import ELFFile from elftools.elf.sections import SymbolTableSection import elftools.elf.enums
if version.parse(elftools.version) < version.parse('0.24'): sys.exit("pyelftools is out of date, need version 0.24 or later")
scr = os.path.basename(sys.argv[0])
def parse_args(): global args
class Slot: def init(self, elf, section): symbols = {sym.name: sym.entry.st_value for sym in section.iter_symbols()} assert "ebus_namespace_start" in symbols , "required ebus_namespace_start symbol" assert "ebus_namespace_end" in symbols , "required ebus_namespace_end symbol" assert "ebus_subscriber_start" in symbols , "required ebus_subscriber_start symbol" assert "ebus_subscriber_end" in symbols , "required ebus_subscriber_end symbol"
class Subscriber: def init(self, sym, event, namespace, subscriber): self.sym = sym self.event = event self.namespace = namespace self.subscriber = subscriber self.ebus = (self.namespace << 16) | event
-- GPERF generation logic
header = """%compare-lengths %define lookup-function-name z_ebus_entry_lookup %readonly-tables %global-table %language=ANSI-C %struct-type %{
include
include
%} struct ebus_entry;
struct ebus_entry { const char name; const uint32_t subscriber; };
"""
Different versions of gperf have different prototypes for the lookup
function, best to implement the wrapper here. The pointer value itself is
turned into a string, we told gperf to expect binary strings that are not
NULL-terminated.
footer = """%% __ebustext const uint32_t z_ebus_subscriber_lookup(uintptr_t ebus) { static const uint32_t dummy = { -1 }; const struct ebus_entry entry = z_ebus_entry_lookup((const char )ebus, sizeof(void )); if (entry) { return entry->subscriber; }
} """
def write_gperf(fp, subscribers): sber_tbl = "" ebus_tbl = "" fp.write(header) for ebus, sber in subscribers.items(): count = 0 sber_tbl += f"static const uint32_t __ebus_ordsubscriber{ebus:08X}[] = {{" for it in sber: sber_tbl += f"{' ' if count % 16 else '\n\t'}{it.subscriber:d}," count = count + 1
def main(): parse_args()
if name == "main": main()
ebus-rom.ld
ebus/CMakeLists.txt
enum { POWER_ON, POWER_OFF, };
/ power.c / EBUS_NAMESPACE_REGISTER(power);
/ subscribe.c /
include "power.h"
static void power_on(uint32_t ebus, void userdata, void appendix) { printk("power on: %s\n", (const char *)appendix); } EBUS_SUBSCRIBE(power, POWER_ON, power_on, NULL);
/ publish.c /
include "power.h"
static struct ebus_publish msg = {"hello", NULL}; void foo(void) { ... EBUS_PUBLISH(power, POWER_ON, &pub); ... }