Re: [PATCH v8 2/9] firmware: arm_scmi: Add notification callbacks-registration
From: Cristian Marussi
Date: Wed Jun 17 2020 - 19:20:38 EST
On Mon, Jun 08, 2020 at 06:03:17PM +0100, Sudeep Holla wrote:
> On Wed, May 20, 2020 at 09:11:11AM +0100, Cristian Marussi wrote:
> > Add core SCMI Notifications callbacks-registration support: allow users
> > to register their own callbacks against the desired events.
> > Whenever a registration request is issued against a still non existent
> > event, mark such request as pending for later processing, in order to
> > account for possible late initializations of SCMI Protocols associated
> > to loadable drivers.
> >
> > Reviewed-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx>
> > Signed-off-by: Cristian Marussi <cristian.marussi@xxxxxxx>
> > ---
> > V7 --> V8
> > - Fixed init check, un-needed atomics removed
> > V6 --> V7
> > - removed un-needed ktime.h include
> > V4 --> V5
> > - fix kernel-doc
> > - reviewed REVT_NOTIFY_ENABLE macro
> > - added matching barrier for late_init
> > V3 --> V4
> > - split registered_handlers hashtable on a per-protocol basis to reduce
> > unneeded contention
> > - introduced pending_handlers table and related late_init worker to finalize
> > handlers registration upon effective protocols' registrations
> > - introduced further safe accessors macros for registered_protocols
> > and registered_events arrays
> > V2 --> V3
> > - refactored get/put event_handler
> > - removed generic non-handle-based API
> > V1 --> V2
> > - splitted out of V1 patch 04
> > - moved from IDR maps to real HashTables to store event_handlers
> > - added proper enable_events refcounting via __scmi_enable_evt()
> > [was broken in V1 when using ALL_SRCIDs notification chains]
> > - reviewed hashtable cleanup strategy in scmi_notifications_exit()
> > - added scmi_register_event_notifier()/scmi_unregister_event_notifier()
> > to include/linux/scmi_protocol.h as a candidate user API
> > [no EXPORTs still]
> > - added notify_ops to handle during initialization as an additional
> > internal API for scmi_drivers
> > ---
> > drivers/firmware/arm_scmi/notify.c | 695 +++++++++++++++++++++++++++++
> > drivers/firmware/arm_scmi/notify.h | 12 +
> > include/linux/scmi_protocol.h | 46 ++
> > 3 files changed, 753 insertions(+)
> >
> > diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
> > index 8b620fc7df50..7cf61dbe2a8e 100644
> > --- a/drivers/firmware/arm_scmi/notify.c
> > +++ b/drivers/firmware/arm_scmi/notify.c
> > @@ -19,17 +19,49 @@
> > * this core its set of supported events using scmi_register_protocol_events():
> > * all the needed descriptors are stored in the &struct registered_protocols and
> > * &struct registered_events arrays.
> > + *
> > + * Kernel users interested in some specific event can register their callbacks
> > + * providing the usual notifier_block descriptor, since this core implements
> > + * events' delivery using the standard Kernel notification chains machinery.
> > + *
> > + * Given the number of possible events defined by SCMI and the extensibility
> > + * of the SCMI Protocol itself, the underlying notification chains are created
> > + * and destroyed dynamically on demand depending on the number of users
> > + * effectively registered for an event, so that no support structures or chains
> > + * are allocated until at least one user has registered a notifier_block for
> > + * such event. Similarly, events' generation itself is enabled at the platform
> > + * level only after at least one user has registered, and it is shutdown after
> > + * the last user for that event has gone.
> > + *
> > + * All users provided callbacks and allocated notification-chains are stored in
> > + * the @registered_events_handlers hashtable. Callbacks' registration requests
> > + * for still to be registered events are instead kept in the dedicated common
> > + * hashtable @pending_events_handlers.
> > + *
> > + * An event is identified univocally by the tuple (proto_id, evt_id, src_id)
> > + * and is served by its own dedicated notification chain; information contained
> > + * in such tuples is used, in a few different ways, to generate the needed
> > + * hash-keys.
> > + *
> > + * Here proto_id and evt_id are simply the protocol_id and message_id numbers
> > + * as described in the SCMI Protocol specification, while src_id represents an
> > + * optional, protocol dependent, source identifier (like domain_id, perf_id
> > + * or sensor_id and so forth).
> > */
> >
> > #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> >
> > +#include <linux/bitfield.h>
> > #include <linux/bug.h>
> > #include <linux/compiler.h>
> > #include <linux/device.h>
> > #include <linux/err.h>
> > +#include <linux/hashtable.h>
> > #include <linux/kernel.h>
> > #include <linux/kfifo.h>
> > +#include <linux/list.h>
> > #include <linux/mutex.h>
> > +#include <linux/notifier.h>
> > #include <linux/refcount.h>
> > #include <linux/scmi_protocol.h>
> > #include <linux/slab.h>
> > @@ -49,6 +81,74 @@
> > #define MAKE_ALL_SRCS_KEY(p, e) \
> > MAKE_HASH_KEY((p), (e), SCMI_ALL_SRC_IDS)
> >
> > +/*
> > + * Assumes that the stored obj includes its own hash-key in a field named 'key':
> > + * with this simplification this macro can be equally used for all the objects'
> > + * types hashed by this implementation.
> > + *
> > + * @__ht: The hashtable name
> > + * @__obj: A pointer to the object type to be retrieved from the hashtable;
> > + * it will be used as a cursor while scanning the hastable and it will
> > + * be possibly left as NULL when @__k is not found
> > + * @__k: The key to search for
> > + */
> > +#define KEY_FIND(__ht, __obj, __k) \
> > +({ \
> > + hash_for_each_possible((__ht), (__obj), hash, (__k)) \
> > + if (likely((__obj)->key == (__k))) \
> > + break; \
> > + __obj; \
> > +})
> > +
> > +#define PROTO_ID_MASK GENMASK(31, 24)
> > +#define EVT_ID_MASK GENMASK(23, 16)
> > +#define SRC_ID_MASK GENMASK(15, 0)
> > +#define KEY_XTRACT_PROTO_ID(key) FIELD_GET(PROTO_ID_MASK, (key))
> > +#define KEY_XTRACT_EVT_ID(key) FIELD_GET(EVT_ID_MASK, (key))
> > +#define KEY_XTRACT_SRC_ID(key) FIELD_GET(SRC_ID_MASK, (key))
> > +
>
> You could move this to patch 1 and improve macros as explained there.
>
Done.
> > +/*
> > + * A set of macros used to access safely @registered_protocols and
> > + * @registered_events arrays; these are fixed in size and each entry is possibly
> > + * populated at protocols' registration time and then only read but NEVER
> > + * modified or removed.
> > + */
> > +#define SCMI_GET_PROTO(__ni, __pid) \
> > +({ \
> > + struct scmi_registered_protocol_events_desc *__pd = NULL; \
> > + \
> > + if ((__ni) && (__pid) < SCMI_MAX_PROTO) \
> > + __pd = READ_ONCE((__ni)->registered_protocols[(__pid)]);\
> > + __pd; \
> > +})
> > +
> > +#define SCMI_GET_REVT_FROM_PD(__pd, __eid) \
> > +({ \
> > + struct scmi_registered_event *__revt = NULL; \
> > + \
> > + if ((__pd) && (__eid) < (__pd)->num_events) \
> > + __revt = READ_ONCE((__pd)->registered_events[(__eid)]); \
> > + __revt; \
> > +})
> > +
> > +#define SCMI_GET_REVT(__ni, __pid, __eid) \
> > +({ \
> > + struct scmi_registered_event *__revt = NULL; \
> > + struct scmi_registered_protocol_events_desc *__pd = NULL; \
>
> Do we need NULL assignment above ? The 2 macros deal with that already.
>
No in fact. I'll remove.
> > + \
> > + __pd = SCMI_GET_PROTO((__ni), (__pid)); \
> > + __revt = SCMI_GET_REVT_FROM_PD(__pd, (__eid)); \
> > + __revt; \
> > +})
> > +
> > +/* A couple of utility macros to limit cruft when calling protocols' helpers */
> > +#define REVT_NOTIFY_ENABLE(revt, eid, sid) \
> > + ((revt)->proto->ops->set_notify_enabled((revt)->proto->ni->handle, \
> > + (eid), (sid), true))
> > +#define REVT_NOTIFY_DISABLE(revt, eid, sid) \
> > + ((revt)->proto->ops->set_notify_enabled((revt)->proto->ni->handle, \
> > + (eid), (sid), false))
> > +
> > struct scmi_registered_protocol_events_desc;
> >
> > /**
> > @@ -56,9 +156,13 @@ struct scmi_registered_protocol_events_desc;
> > * core
> > * @gid: GroupID used for devres
> > * @handle: A reference to the platform instance
> > + * @init_work: A work item to perform final initializations of pending handlers
> > + * @pending_mtx: A mutex to protect @pending_events_handlers
> > * @registered_protocols: A statically allocated array containing pointers to
> > * all the registered protocol-level specific information
> > * related to events' handling
> > + * @pending_events_handlers: An hashtable containing all pending events'
> > + * handlers descriptors
> > *
> > * Each platform instance, represented by a handle, has its own instance of
> > * the notification subsystem represented by this structure.
> > @@ -66,7 +170,12 @@ struct scmi_registered_protocol_events_desc;
> > struct scmi_notify_instance {
> > void *gid;
> > struct scmi_handle *handle;
> > +
> > + struct work_struct init_work;
> > +
> > + struct mutex pending_mtx;
> > struct scmi_registered_protocol_events_desc **registered_protocols;
> > + DECLARE_HASHTABLE(pending_events_handlers, 8);
>
> What's the magic 8 here ? May be worth adding comment or reuse some macro
> if already defined ?
>
It determines the size of the hashtable (of the underlying array in fact)
as 1 << 8; 256 seemed a reasonable value to avoid most collisions since we
have 256 protocols at max and not expecting so many registerd users' callback
for each.
Avoiding collisions possibly reduces some undeterministic spike in the time
needed to deliver an event but it's anyway looked up in the deferred worker and
probably overkill as 256 (especially the pending one), so I'll reduce those
sizes (reducing size of possibly empty underlying htable arrays) and use some
meaningful macros to identify those.
> > };
> >
> > /**
> > @@ -115,6 +224,9 @@ struct scmi_registered_event;
> > * @registered_events: A dynamically allocated array holding all the registered
> > * events' descriptors, whose fixed-size is determined at
> > * compile time.
> > + * @registered_mtx: A mutex to protect @registered_events_handlers
> > + * @registered_events_handlers: An hashtable containing all events' handlers
> > + * descriptors registered for this protocol
> > *
> > * All protocols that register at least one event have their protocol-specific
> > * information stored here, together with the embedded allocated events_queue.
> > @@ -135,6 +247,8 @@ struct scmi_registered_protocol_events_desc {
> > void *in_flight;
> > int num_events;
> > struct scmi_registered_event **registered_events;
> > + struct mutex registered_mtx;
> > + DECLARE_HASHTABLE(registered_events_handlers, 8);
> > };
> >
>
> Ditto
>
I'll fix as above.
> > /**
> > @@ -166,6 +280,38 @@ struct scmi_registered_event {
> > struct mutex sources_mtx;
> > };
> >
> > +/**
> > + * struct scmi_event_handler - Event handler information
> > + * @key: The used hashkey
> > + * @users: A reference count for number of active users for this handler
> > + * @r_evt: A reference to the associated registered event; when this is NULL
> > + * this handler is pending, which means that identifies a set of
> > + * callbacks intended to be attached to an event which is still not
> > + * known nor registered by any protocol at that point in time
> > + * @chain: The notification chain dedicated to this specific event tuple
> > + * @hash: The hlist_node used for collision handling
> > + * @enabled: A boolean which records if event's generation has been already
> > + * enabled for this handler as a whole
> > + *
> > + * This structure collects all the information needed to process a received
> > + * event identified by the tuple (proto_id, evt_id, src_id).
> > + * These descriptors are stored in a per-protocol @registered_events_handlers
> > + * table using as a key a value derived from that tuple.
> > + */
> > +struct scmi_event_handler {
> > + u32 key;
> > + refcount_t users;
> > + struct scmi_registered_event *r_evt;
> > + struct blocking_notifier_head chain;
> > + struct hlist_node hash;
> > + bool enabled;
> > +};
> > +
> > +#define IS_HNDL_PENDING(hndl) ((hndl)->r_evt == NULL)
> > +
> > +static void scmi_put_handler_unlocked(struct scmi_notify_instance *ni,
> > + struct scmi_event_handler *hndl);
> > +
> > /**
> > * scmi_kfifo_free() - Devres action helper to free the kfifo
> > * @kfifo: The kfifo to free
> > @@ -253,6 +399,10 @@ scmi_allocate_registered_protocol_desc(struct scmi_notify_instance *ni,
> > return ERR_PTR(-ENOMEM);
> > pd->num_events = num_events;
> >
> > + /* Initialize per protocol handlers table */
> > + mutex_init(&pd->registered_mtx);
> > + hash_init(pd->registered_events_handlers);
> > +
> > return pd;
> > }
> >
> > @@ -343,6 +493,12 @@ int scmi_register_protocol_events(const struct scmi_handle *handle,
> >
> > devres_close_group(ni->handle->dev, ni->gid);
> >
> > + /*
> > + * Finalize any pending events' handler which could have been waiting
> > + * for this protocol's events registration.
> > + */
> > + schedule_work(&ni->init_work);
> > +
> > return 0;
> >
> > err:
> > @@ -354,6 +510,539 @@ int scmi_register_protocol_events(const struct scmi_handle *handle,
> > return -ENOMEM;
> > }
> >
> > +/**
> > + * scmi_allocate_event_handler() - Allocate Event handler
> > + * @ni: A reference to the notification instance to use
> > + * @evt_key: 32bit key uniquely bind to the event identified by the tuple
> > + * (proto_id, evt_id, src_id)
> > + *
> > + * Allocate an event handler and related notification chain associated with
> > + * the provided event handler key.
> > + * Note that, at this point, a related registered_event is still to be
> > + * associated to this handler descriptor (hndl->r_evt == NULL), so the handler
> > + * is initialized as pending.
> > + *
> > + * Context: Assumes to be called with @pending_mtx already acquired.
> > + * Return: the freshly allocated structure on Success
> > + */
> > +static struct scmi_event_handler *
> > +scmi_allocate_event_handler(struct scmi_notify_instance *ni, u32 evt_key)
> > +{
> > + struct scmi_event_handler *hndl;
> > +
> > + hndl = kzalloc(sizeof(*hndl), GFP_KERNEL);
> > + if (!hndl)
> > + return ERR_PTR(-ENOMEM);
> > + hndl->key = evt_key;
> > + BLOCKING_INIT_NOTIFIER_HEAD(&hndl->chain);
> > + refcount_set(&hndl->users, 1);
> > + /* New handlers are created pending */
> > + hash_add(ni->pending_events_handlers, &hndl->hash, hndl->key);
> > +
> > + return hndl;
> > +}
> > +
> > +/**
> > + * scmi_free_event_handler() - Free the provided Event handler
> > + * @hndl: The event handler structure to free
> > + *
> > + * Context: Assumes to be called with proper locking acquired depending
> > + * on the situation.
> > + */
> > +static void scmi_free_event_handler(struct scmi_event_handler *hndl)
> > +{
> > + hash_del(&hndl->hash);
> > + kfree(hndl);
> > +}
> > +
> > +/**
> > + * scmi_bind_event_handler() - Helper to attempt binding an handler to an event
> > + * @ni: A reference to the notification instance to use
> > + * @hndl: The event handler to bind
> > + *
> > + * If an associated registered event is found, move the handler from the pending
> > + * into the registered table.
> > + *
> > + * Context: Assumes to be called with @pending_mtx already acquired.
> > + * Return: True if bind was successful, False otherwise
> > + */
> > +static inline bool scmi_bind_event_handler(struct scmi_notify_instance *ni,
> > + struct scmi_event_handler *hndl)
> > +{
> > + struct scmi_registered_event *r_evt;
> > +
> > +
> > + r_evt = SCMI_GET_REVT(ni, KEY_XTRACT_PROTO_ID(hndl->key),
> > + KEY_XTRACT_EVT_ID(hndl->key));
> > + if (unlikely(!r_evt))
> > + return false;
> > +
> > + /* Remove from pending and insert into registered */
> > + hash_del(&hndl->hash);
> > + hndl->r_evt = r_evt;
> > + mutex_lock(&r_evt->proto->registered_mtx);
> > + hash_add(r_evt->proto->registered_events_handlers,
> > + &hndl->hash, hndl->key);
> > + mutex_unlock(&r_evt->proto->registered_mtx);
> > +
> > + return true;
> > +}
> > +
> > +/**
> > + * scmi_valid_pending_handler() - Helper to check pending status of handlers
> > + * @ni: A reference to the notification instance to use
> > + * @hndl: The event handler to check
> > + *
> > + * An handler is considered pending when its r_evt == NULL, because the related
> > + * event was still unknown at handler's registration time; anyway, since all
> > + * protocols register their supported events once for all at protocols'
> > + * initialization time, a pending handler cannot be considered valid anymore if
> > + * the underlying event (which it is waiting for), belongs to an already
> > + * initialized and registered protocol.
> > + *
> > + * Return: True if pending registration is still valid, False otherwise.
> > + */
> > +static inline bool scmi_valid_pending_handler(struct scmi_notify_instance *ni,
> > + struct scmi_event_handler *hndl)
> > +{
> > + struct scmi_registered_protocol_events_desc *pd;
> > +
> > + if (unlikely(!IS_HNDL_PENDING(hndl)))
> > + return false;
> > +
> > + pd = SCMI_GET_PROTO(ni, KEY_XTRACT_PROTO_ID(hndl->key));
> > + if (pd)
> > + return false;
> > +
> > + return true;
> > +}
> > +
> > +/**
> > + * scmi_register_event_handler() - Register whenever possible an Event handler
> > + * @ni: A reference to the notification instance to use
> > + * @hndl: The event handler to register
> > + *
> > + * At first try to bind an event handler to its associated event, then check if
> > + * it was at least a valid pending handler: if it was not bound nor valid return
> > + * false.
> > + *
> > + * Valid pending incomplete bindings will be periodically retried by a dedicated
> > + * worker which is kicked each time a new protocol completes its own
> > + * registration phase.
> > + *
> > + * Context: Assumes to be called with @pending_mtx acquired.
> > + * Return: True if a normal or a valid pending registration has been completed,
> > + * False otherwise
> > + */
> > +static bool scmi_register_event_handler(struct scmi_notify_instance *ni,
> > + struct scmi_event_handler *hndl)
> > +{
> > + bool ret;
> > +
> > + ret = scmi_bind_event_handler(ni, hndl);
> > + if (ret) {
> > + pr_info("SCMI Notifications: registered NEW handler - key:%X\n",
> > + hndl->key);
> > + } else {
> > + ret = scmi_valid_pending_handler(ni, hndl);
> > + if (ret)
> > + pr_info("SCMI Notifications: registered PENDING handler - key:%X\n",
> > + hndl->key);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/**
> > + * __scmi_event_handler_get_ops() - Utility to get or create an event handler
> > + * @ni: A reference to the notification instance to use
> > + * @evt_key: The event key to use
> > + * @create: A boolean flag to specify if a handler must be created when
> > + * not already existent
> > + *
> > + * Search for the desired handler matching the key in both the per-protocol
> > + * registered table and the common pending table:
> > + * * if found adjust users refcount
> > + * * if not found and @create is true, create and register the new handler:
> > + * handler could end up being registered as pending if no matching event
> > + * could be found.
> > + *
> > + * An handler is guaranteed to reside in one and only one of the tables at
> > + * any one time; to ensure this the whole search and create is performed
> > + * holding the @pending_mtx lock, with @registered_mtx additionally acquired
> > + * if needed.
> > + *
> > + * Note that when a nested acquisition of these mutexes is needed the locking
> > + * order is always (same as in @init_work):
> > + * 1. pending_mtx
> > + * 2. registered_mtx
> > + *
> > + * Events generation is NOT enabled right after creation within this routine
> > + * since at creation time we usually want to have all setup and ready before
> > + * events really start flowing.
> > + *
> > + * Return: A properly refcounted handler on Success, NULL on Failure
> > + */
> > +static inline struct scmi_event_handler *
> > +__scmi_event_handler_get_ops(struct scmi_notify_instance *ni,
> > + u32 evt_key, bool create)
> > +{
> > + struct scmi_registered_event *r_evt;
> > + struct scmi_event_handler *hndl = NULL;
> > +
> > + r_evt = SCMI_GET_REVT(ni, KEY_XTRACT_PROTO_ID(evt_key),
> > + KEY_XTRACT_EVT_ID(evt_key));
> > +
> > + mutex_lock(&ni->pending_mtx);
> > + /* Search registered events at first ... if possible at all */
> > + if (likely(r_evt)) {
> > + mutex_lock(&r_evt->proto->registered_mtx);
> > + hndl = KEY_FIND(r_evt->proto->registered_events_handlers,
> > + hndl, evt_key);
> > + if (likely(hndl))
> > + refcount_inc(&hndl->users);
> > + mutex_unlock(&r_evt->proto->registered_mtx);
> > + }
> > +
> > + /* ...then amongst pending. */
> > + if (unlikely(!hndl)) {
> > + hndl = KEY_FIND(ni->pending_events_handlers, hndl, evt_key);
> > + if (likely(hndl))
> > + refcount_inc(&hndl->users);
> > + }
> > +
> > + /* Create if still not found and required */
> > + if (!hndl && create) {
> > + hndl = scmi_allocate_event_handler(ni, evt_key);
> > + if (!IS_ERR_OR_NULL(hndl)) {
> > + if (!scmi_register_event_handler(ni, hndl)) {
> > + pr_info("SCMI Notifications: purging UNKNOWN handler - key:%X\n",
> > + hndl->key);
> > + /* this hndl can be only a pending one */
> > + scmi_put_handler_unlocked(ni, hndl);
> > + hndl = NULL;
> > + }
> > + }
> > + }
> > + mutex_unlock(&ni->pending_mtx);
> > +
> > + return hndl;
> > +}
> > +
> > +static struct scmi_event_handler *
> > +scmi_get_handler(struct scmi_notify_instance *ni, u32 evt_key)
> > +{
> > + return __scmi_event_handler_get_ops(ni, evt_key, false);
> > +}
> > +
> > +static struct scmi_event_handler *
> > +scmi_get_or_create_handler(struct scmi_notify_instance *ni, u32 evt_key)
> > +{
> > + return __scmi_event_handler_get_ops(ni, evt_key, true);
> > +}
> > +
> > +/**
> > + * __scmi_enable_evt() - Enable/disable events generation
> > + * @r_evt: The registered event to act upon
> > + * @src_id: The src_id to act upon
> > + * @enable: The action to perform: true->Enable, false->Disable
> > + *
> > + * Takes care of proper refcounting while performing enable/disable: handles
> > + * the special case of ALL sources requests by itself.
> > + *
> > + * Return: True when the required action has been successfully executed
> > + */
> > +static inline bool __scmi_enable_evt(struct scmi_registered_event *r_evt,
> > + u32 src_id, bool enable)
> > +{
> > + int ret = 0;
> > + u32 num_sources;
> > + refcount_t *sid;
> > +
> > + if (src_id == SCMI_ALL_SRC_IDS) {
> > + src_id = 0;
> > + num_sources = r_evt->num_sources;
> > + } else if (src_id < r_evt->num_sources) {
> > + num_sources = 1;
> > + } else {
> > + return ret;
> > + }
> > +
> > + mutex_lock(&r_evt->sources_mtx);
> > + if (enable) {
> > + for (; num_sources; src_id++, num_sources--) {
> > + bool r;
> > +
> > + sid = &r_evt->sources[src_id];
> > + if (refcount_read(sid) == 0) {
> > + r = REVT_NOTIFY_ENABLE(r_evt,
> > + r_evt->evt->id, src_id);
> > + if (r)
> > + refcount_set(sid, 1);
> > + } else {
> > + refcount_inc(sid);
> > + r = true;
> > + }
> > + ret += r;
> > + }
> > + } else {
> > + for (; num_sources; src_id++, num_sources--) {
> > + sid = &r_evt->sources[src_id];
> > + if (refcount_dec_and_test(sid))
> > + REVT_NOTIFY_DISABLE(r_evt,
> > + r_evt->evt->id, src_id);
> > + }
> > + ret = 1;
> > + }
> > + mutex_unlock(&r_evt->sources_mtx);
> > +
> > + return ret;
> > +}
> > +
> > +static bool scmi_enable_events(struct scmi_event_handler *hndl)
> > +{
> > + if (!hndl->enabled)
> > + hndl->enabled = __scmi_enable_evt(hndl->r_evt,
> > + KEY_XTRACT_SRC_ID(hndl->key),
> > + true);
> > + return hndl->enabled;
> > +}
> > +
> > +static bool scmi_disable_events(struct scmi_event_handler *hndl)
> > +{
> > + if (hndl->enabled)
> > + hndl->enabled = !__scmi_enable_evt(hndl->r_evt,
> > + KEY_XTRACT_SRC_ID(hndl->key),
> > + false);
> > + return !hndl->enabled;
> > +}
> > +
> > +/**
> > + * scmi_put_handler_unlocked() - Put an event handler
> > + * @ni: A reference to the notification instance to use
> > + * @hndl: The event handler to act upon
> > + *
> > + * After having got exclusive access to the registered handlers hashtable,
> > + * update the refcount and if @hndl is no more in use by anyone:
> > + * * ask for events' generation disabling
> > + * * unregister and free the handler itself
> > + *
> > + * Context: Assumes all the proper locking has been managed by the caller.
> > + */
> > +static void
> > +scmi_put_handler_unlocked(struct scmi_notify_instance *ni,
> > + struct scmi_event_handler *hndl)
> > +{
> > + if (refcount_dec_and_test(&hndl->users)) {
> > + if (likely(!IS_HNDL_PENDING(hndl)))
> > + scmi_disable_events(hndl);
> > + scmi_free_event_handler(hndl);
> > + }
> > +}
> > +
> > +static void scmi_put_handler(struct scmi_notify_instance *ni,
> > + struct scmi_event_handler *hndl)
> > +{
> > + struct scmi_registered_event *r_evt = hndl->r_evt;
> > +
> > + mutex_lock(&ni->pending_mtx);
> > + if (r_evt)
> > + mutex_lock(&r_evt->proto->registered_mtx);
> > +
> > + scmi_put_handler_unlocked(ni, hndl);
> > +
> > + if (r_evt)
> > + mutex_unlock(&r_evt->proto->registered_mtx);
> > + mutex_unlock(&ni->pending_mtx);
> > +}
> > +
> > +/**
> > + * scmi_event_handler_enable_events() - Enable events associated to an handler
> > + * @hndl: The Event handler to act upon
> > + *
> > + * Return: True on success
> > + */
> > +static bool scmi_event_handler_enable_events(struct scmi_event_handler *hndl)
> > +{
> > + if (!scmi_enable_events(hndl)) {
> > + pr_err("SCMI Notifications: Failed to ENABLE events for key:%X !\n",
> > + hndl->key);
> > + return false;
> > + }
> > +
> > + return true;
> > +}
> > +
> > +/**
> > + * scmi_register_notifier() - Register a notifier_block for an event
> > + * @handle: The handle identifying the platform instance against which the
> > + * callback is registered
> > + * @proto_id: Protocol ID
> > + * @evt_id: Event ID
> > + * @src_id: Source ID, when NULL register for events coming form ALL possible
> > + * sources
> > + * @nb: A standard notifier block to register for the specified event
> > + *
> > + * Generic helper to register a notifier_block against a protocol event.
> > + *
> > + * A notifier_block @nb will be registered for each distinct event identified
> > + * by the tuple (proto_id, evt_id, src_id) on a dedicated notification chain
> > + * so that:
> > + *
> > + * (proto_X, evt_Y, src_Z) --> chain_X_Y_Z
> > + *
> > + * @src_id meaning is protocol specific and identifies the origin of the event
> > + * (like domain_id, sensor_id and so forth).
> > + *
> > + * @src_id can be NULL to signify that the caller is interested in receiving
> > + * notifications from ALL the available sources for that protocol OR simply that
> > + * the protocol does not support distinct sources.
> > + *
> > + * As soon as one user for the specified tuple appears, an handler is created,
> > + * and that specific event's generation is enabled at the platform level, unless
> > + * an associated registered event is found missing, meaning that the needed
> > + * protocol is still to be initialized and the handler has just been registered
> > + * as still pending.
> > + *
> > + * Return: Return 0 on Success
> > + */
> > +static int scmi_register_notifier(const struct scmi_handle *handle,
> > + u8 proto_id, u8 evt_id, u32 *src_id,
> > + struct notifier_block *nb)
> > +{
> > + int ret = 0;
> > + u32 evt_key;
> > + struct scmi_event_handler *hndl;
> > + struct scmi_notify_instance *ni;
> > +
> > + /* Ensure notify_priv is updated */
> > + smp_rmb();
> > + if (unlikely(!handle->notify_priv))
> > + return -ENODEV;
> > + ni = handle->notify_priv;
> > +
> > + evt_key = MAKE_HASH_KEY(proto_id, evt_id,
> > + src_id ? *src_id : SCMI_ALL_SRC_IDS);
> > + hndl = scmi_get_or_create_handler(ni, evt_key);
> > + if (IS_ERR_OR_NULL(hndl))
> > + return PTR_ERR(hndl);
> > +
> > + blocking_notifier_chain_register(&hndl->chain, nb);
> > +
> > + /* Enable events for not pending handlers */
> > + if (likely(!IS_HNDL_PENDING(hndl))) {
> > + if (!scmi_event_handler_enable_events(hndl)) {
> > + scmi_put_handler(ni, hndl);
> > + ret = -EINVAL;
> > + }
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/**
> > + * scmi_unregister_notifier() - Unregister a notifier_block for an event
> > + * @handle: The handle identifying the platform instance against which the
> > + * callback is unregistered
> > + * @proto_id: Protocol ID
> > + * @evt_id: Event ID
> > + * @src_id: Source ID
> > + * @nb: The notifier_block to unregister
> > + *
> > + * Takes care to unregister the provided @nb from the notification chain
> > + * associated to the specified event and, if there are no more users for the
> > + * event handler, frees also the associated event handler structures.
> > + * (this could possibly cause disabling of event's generation at platform level)
> > + *
> > + * Return: 0 on Success
> > + */
> > +static int scmi_unregister_notifier(const struct scmi_handle *handle,
> > + u8 proto_id, u8 evt_id, u32 *src_id,
> > + struct notifier_block *nb)
> > +{
> > + u32 evt_key;
> > + struct scmi_event_handler *hndl;
> > + struct scmi_notify_instance *ni;
> > +
> > + /* Ensure notify_priv is updated */
> > + smp_rmb();
> > + if (unlikely(!handle->notify_priv))
> > + return -ENODEV;
> > + ni = handle->notify_priv;
> > +
> > + evt_key = MAKE_HASH_KEY(proto_id, evt_id,
> > + src_id ? *src_id : SCMI_ALL_SRC_IDS);
> > + hndl = scmi_get_handler(ni, evt_key);
> > + if (IS_ERR_OR_NULL(hndl))
> > + return -EINVAL;
> > +
> > + blocking_notifier_chain_unregister(&hndl->chain, nb);
> > + scmi_put_handler(ni, hndl);
> > +
> > + /*
> > + * Free the handler (and stop events) if this happens to be the last
> > + * known user callback for this handler; a possible concurrently ongoing
> > + * run of @scmi_lookup_and_call_event_chain will cause this to happen
> > + * in that context safely instead.
> > + */
>
> Sorry but I don't follow this as the comment states some condition while
> this is done unconditionally. So each scmi_unregister_notifier decrements
> refcount by 2 ?
>
Basically yes...the explanation is not so short though (:D) and I tried to badly
summarized it in the comment above.
Events dispatching is handled with custom dynamically created notification
chains; such chains are created on demand as soon as the first user registers a
notifier against such event/chain and destroyed whenever the last user deregisters
the last notifier callback for the same chain: such chains and related data are
stored in an event_handler struct and every currently existing handler is stored
in a per-protocol hashtable.
Beside register/unregister ops such htable can be concurrently looked up in search
of a matching handler also during the dispatching of the received events in the
deferred workqueues; the access to the hashtable itself is protected by a table
mutex (acquired and released inside each get/put) and the handler itself is reference
counted to effectively track whenever is no more required by any user and can be freed.
So upon each get/put the mutex is temporarily acquired to atomically access the htable
and adjust the handler refcount and then released: if, at register time did not exist
already, it is created straight away with refcount 1.
But, the important thing is that, for this to work, wherever an handler is accessed
it has to be get at first and then put when done; this happens also during the delivery
phase, so that, if I'm looking up event X for delivery and some user is unregistering
those last callback for event X, I'm sure that the current lookup won't unexpectedly see
its handler vanishing while operating on it.
In such a racy case the unregister completes regularly on its side but the refcount does
not drop to zero because there is still one pending (get) lookup ongoing: once the lookup
completes and issues its own put, the handler will be finally freed in the lookup context.
So, finally, looking at how this function behaves, I have to put the handler twice, one at
first to balance the get issued early inside this same function to access the handler and
then another time to balance the get issued previously inside the register routine: if this
was the last user, in a normal non-racy scenario, this will cause, here, the refcount to
drop to zero and the handler to be freed straight away, while, in the racy scenario depicted
above, it will be the final put issue by the completion of the concurrent lookup ops that
will finally zero the refcount and free the handler.
I'll try to review the above comment in a more sensible way: my point was to give some
explanation of the double put in a concise way....does not seem to have had success :D
> > + scmi_put_handler(ni, hndl);
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * scmi_protocols_late_init() - Worker for late initialization
> > + * @work: The work item to use associated to the proper SCMI instance
> > + *
> > + * This kicks in whenever a new protocol has completed its own registration via
> > + * scmi_register_protocol_events(): it is in charge of scanning the table of
> > + * pending handlers (registered by users while the related protocol was still
> > + * not initialized) and finalizing their initialization whenever possible;
> > + * invalid pending handlers are purged at this point in time.
> > + */
> > +static void scmi_protocols_late_init(struct work_struct *work)
> > +{
> > + int bkt;
> > + struct scmi_event_handler *hndl;
> > + struct scmi_notify_instance *ni;
> > + struct hlist_node *tmp;
> > +
> > + ni = container_of(work, struct scmi_notify_instance, init_work);
> > +
> > + /* Ensure protocols and events are up to date */
> > + smp_rmb();
> > +
> > + mutex_lock(&ni->pending_mtx);
> > + hash_for_each_safe(ni->pending_events_handlers, bkt, tmp, hndl, hash) {
> > + bool ret;
> > +
> > + ret = scmi_bind_event_handler(ni, hndl);
> > + if (ret) {
> > + pr_info("SCMI Notifications: finalized PENDING handler - key:%X\n",
> > + hndl->key);
> > + ret = scmi_event_handler_enable_events(hndl);
> > + } else {
> > + ret = scmi_valid_pending_handler(ni, hndl);
> > + }
> > + if (!ret) {
> > + pr_info("SCMI Notifications: purging PENDING handler - key:%X\n",
> > + hndl->key);
>
> Again all these can be debug logs.
>
Fine I'll move almost all to dev_dbg()
> > + /* this hndl can be only a pending one */
> > + scmi_put_handler_unlocked(ni, hndl);
> > + }
> > + }
> > + mutex_unlock(&ni->pending_mtx);
> > +}
> > +
> > +/*
> > + * notify_ops are attached to the handle so that can be accessed
> > + * directly from an scmi_driver to register its own notifiers.
> > + */
> > +static struct scmi_notify_ops notify_ops = {
> > + .register_event_notifier = scmi_register_notifier,
> > + .unregister_event_notifier = scmi_unregister_notifier,
> > +};
> > +
> > /**
> > * scmi_notification_init() - Initializes Notification Core Support
> > * @handle: The handle identifying the platform instance to initialize
> > @@ -401,6 +1090,12 @@ int scmi_notification_init(struct scmi_handle *handle)
> > if (!ni->registered_protocols)
> > goto err;
> >
> > + mutex_init(&ni->pending_mtx);
> > + hash_init(ni->pending_events_handlers);
> > +
> > + INIT_WORK(&ni->init_work, scmi_protocols_late_init);
> > +
> > + handle->notify_ops = ¬ify_ops;
> > handle->notify_priv = ni;
> > /* Ensure handle is up to date */
> > smp_wmb();
> > diff --git a/drivers/firmware/arm_scmi/notify.h b/drivers/firmware/arm_scmi/notify.h
> > index 54094aaf812a..f0561fb30970 100644
> > --- a/drivers/firmware/arm_scmi/notify.h
> > +++ b/drivers/firmware/arm_scmi/notify.h
> > @@ -9,9 +9,21 @@
> > #ifndef _SCMI_NOTIFY_H
> > #define _SCMI_NOTIFY_H
> >
> > +#include <linux/bug.h>
> > #include <linux/device.h>
> > #include <linux/types.h>
> >
> > +#define MAP_EVT_TO_ENABLE_CMD(id, map) \
> > +({ \
> > + int ret = -1; \
> > + \
> > + if (likely((id) < ARRAY_SIZE((map)))) \
> > + ret = (map)[(id)]; \
> > + else \
> > + WARN(1, "UN-KNOWN evt_id:%d\n", (id)); \
> > + ret; \
> > +})
> > +
>
> This is used just once, inline it where you use for now.
>
Yes I'll do, it was used more at first then version after version I dropped it
and forgot to drop it here too. (Not sure if it will be used again in other
in_progress SCMI series, but anyway is not worth having it as of now)
Thanks
Cristian
> --
> Regards,
> Sudeep