// SPDX-License-Identifier: GPL-2.0+/* * Main SSAM/SSH controller structure and functionality. * * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/acpi.h>#include <linux/atomic.h>#include <linux/completion.h>#include <linux/gpio/consumer.h>#include <linux/interrupt.h>#include <linux/kref.h>#include <linux/limits.h>#include <linux/list.h>#include <linux/lockdep.h>#include <linux/mutex.h>#include <linux/rculist.h>#include <linux/rbtree.h>#include <linux/rwsem.h>#include <linux/serdev.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/srcu.h>#include <linux/types.h>#include <linux/workqueue.h> #include <linux/surface_aggregator/controller.h>#include <linux/surface_aggregator/serial_hub.h> #include "controller.h"#include "ssh_msgb.h"#include "ssh_request_layer.h" #include "trace.h" /* -- Safe counters. -------------------------------------------------------- */ /** * ssh_seq_reset() - Reset/initialize sequence ID counter. * @c: The counter to reset. */static void ssh_seq_reset(struct ssh_seq_counter *c){ WRITE_ONCE(c->value, 0);} /** * ssh_seq_next() - Get next sequence ID. * @c: The counter providing the sequence IDs. * * Return: Returns the next sequence ID of the counter. */static u8 ssh_seq_next(struct ssh_seq_counter *c){ u8 old = READ_ONCE(c->value); u8 new = old + 1; u8 ret; while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { old = ret; new = old + 1; } return old;} /** * ssh_rqid_reset() - Reset/initialize request ID counter. * @c: The counter to reset. */static void ssh_rqid_reset(struct ssh_rqid_counter *c){ WRITE_ONCE(c->value, 0);} /** * ssh_rqid_next() - Get next request ID. * @c: The counter providing the request IDs. * * Return: Returns the next request ID of the counter, skipping any reserved * request IDs. */static u16 ssh_rqid_next(struct ssh_rqid_counter *c){ u16 old = READ_ONCE(c->value); u16 new = ssh_rqid_next_valid(old); u16 ret; while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { old = ret; new = ssh_rqid_next_valid(old); } return old;} /* -- Event notifier/callbacks. --------------------------------------------- *//* * The notifier system is based on linux/notifier.h, specifically the SRCU * implementation. The difference to that is, that some bits of the notifier * call return value can be tracked across multiple calls. This is done so * that handling of events can be tracked and a warning can be issued in case * an event goes unhandled. The idea of that warning is that it should help * discover and identify new/currently unimplemented features. */ /** * ssam_event_matches_notifier() - Test if an event matches a notifier. * @n: The event notifier to test against. * @event: The event to test. * * Return: Returns %true if the given event matches the given notifier * according to the rules set in the notifier's event mask, %false otherwise. */static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n, const struct ssam_event *event){ bool match = n->event.id.target_category == event->target_category; if (n->event.mask & SSAM_EVENT_MASK_TARGET) match &= n->event.reg.target_id == event->target_id; if (n->event.mask & SSAM_EVENT_MASK_INSTANCE) match &= n->event.id.instance == event->instance_id; return match;} /** * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain. * @nh: The notifier head for which the notifier callbacks should be called. * @event: The event data provided to the callbacks. * * Call all registered notifier callbacks in order of their priority until * either no notifier is left or a notifier returns a value with the * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via * ssam_notifier_from_errno() on any non-zero error value. * * Return: Returns the notifier status value, which contains the notifier * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a * potential error value returned from the last executed notifier callback. * Use ssam_notifier_to_errno() to convert this value to the original error * value. */static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event){ struct ssam_event_notifier *nf; int ret = 0, idx; idx = srcu_read_lock(&nh->srcu); list_for_each_entry_rcu(nf, &nh->head, base.node, srcu_read_lock_held(&nh->srcu)) { if (ssam_event_matches_notifier(nf, event)) { ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event); if (ret & SSAM_NOTIF_STOP) break; } } srcu_read_unlock(&nh->srcu, idx); return ret;} /** * ssam_nfblk_insert() - Insert a new notifier block into the given notifier * list. * @nh: The notifier head into which the block should be inserted. * @nb: The notifier block to add. * * Note: This function must be synchronized by the caller with respect to other * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. * * Return: Returns zero on success, %-EEXIST if the notifier block has already * been registered. */static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb){ struct ssam_notifier_block *p; struct list_head *h; /* Runs under lock, no need for RCU variant. */ list_for_each(h, &nh->head) { p = list_entry(h, struct ssam_notifier_block, node); if (unlikely(p == nb)) { WARN(1, "double register detected"); return -EEXIST; } if (nb->priority > p->priority) break; } list_add_tail_rcu(&nb->node, h); return 0;} /** * ssam_nfblk_find() - Check if a notifier block is registered on the given * notifier head. * list. * @nh: The notifier head on which to search. * @nb: The notifier block to search for. * * Note: This function must be synchronized by the caller with respect to other * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. * * Return: Returns true if the given notifier block is registered on the given * notifier head, false otherwise. */static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb){ struct ssam_notifier_block *p; /* Runs under lock, no need for RCU variant. */ list_for_each_entry(p, &nh->head, node) { if (p == nb) return true; } return false;} /** * ssam_nfblk_remove() - Remove a notifier block from its notifier list. * @nb: The notifier block to be removed. * * Note: This function must be synchronized by the caller with respect to * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. * Furthermore, the caller _must_ ensure SRCU synchronization by calling * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to * ensure that the removed notifier block is not in use any more. */static void ssam_nfblk_remove(struct ssam_notifier_block *nb){ list_del_rcu(&nb->node);} /** * ssam_nf_head_init() - Initialize the given notifier head. * @nh: The notifier head to initialize. */static int ssam_nf_head_init(struct ssam_nf_head *nh){ int status; status = init_srcu_struct(&nh->srcu); if (status) return status; INIT_LIST_HEAD(&nh->head); return 0;} /** * ssam_nf_head_destroy() - Deinitialize the given notifier head. * @nh: The notifier head to deinitialize. */static void ssam_nf_head_destroy(struct ssam_nf_head *nh){ cleanup_srcu_struct(&nh->srcu);} /* -- Event/notification registry. ------------------------------------------ */ /** * struct ssam_nf_refcount_key - Key used for event activation reference * counting. * @reg: The registry via which the event is enabled/disabled. * @id: The ID uniquely describing the event. */struct ssam_nf_refcount_key { struct ssam_event_registry reg; struct ssam_event_id id;}; /** * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event * activations. * @node: The node of this entry in the rb-tree. * @key: The key of the event. * @refcount: The reference-count of the event. * @flags: The flags used when enabling the event. */struct ssam_nf_refcount_entry { struct rb_node node; struct ssam_nf_refcount_key key; int refcount; u8 flags;}; /** * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given * event. * @nf: The notifier system reference. * @reg: The registry used to enable/disable the event. * @id: The event ID. * * Increments the reference-/activation-count associated with the specified * event type/ID, allocating a new entry for this event ID if necessary. A * newly allocated entry will have a refcount of one. * * Note: ``nf->lock`` must be held when calling this function. * * Return: Returns the refcount entry on success. Returns an error pointer * with %-ENOSPC if there have already been %INT_MAX events of the specified * ID and type registered, or %-ENOMEM if the entry could not be allocated. */static struct ssam_nf_refcount_entry *ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg, struct ssam_event_id id){ struct ssam_nf_refcount_entry *entry; struct ssam_nf_refcount_key key; struct rb_node **link = &nf->refcount.rb_node; struct rb_node *parent = NULL; int cmp; lockdep_assert_held(&nf->lock); key.reg = reg; key.id = id; while (*link) { entry = rb_entry(*link, struct ssam_nf_refcount_entry, node); parent = *link; cmp = memcmp(&key, &entry->key, sizeof(key)); if (cmp < 0) { link = &(*link)->rb_left; } else if (cmp > 0) { link = &(*link)->rb_right; } else if (entry->refcount < INT_MAX) { entry->refcount++; return entry; } else { WARN_ON(1); return ERR_PTR(-ENOSPC); } } entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return ERR_PTR(-ENOMEM); entry->key = key; entry->refcount = 1; rb_link_node(&entry->node, parent, link); rb_insert_color(&entry->node, &nf->refcount); return entry;} /** * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given * event. * @nf: The notifier system reference. * @reg: The registry used to enable/disable the event. * @id: The event ID. * * Decrements the reference-/activation-count of the specified event, * returning its entry. If the returned entry has a refcount of zero, the * caller is responsible for freeing it using kfree(). * * Note: ``nf->lock`` must be held when calling this function. * * Return: Returns the refcount entry on success or %NULL if the entry has not * been found. */static struct ssam_nf_refcount_entry *ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, struct ssam_event_id id){ struct ssam_nf_refcount_entry *entry; struct ssam_nf_refcount_key key; struct rb_node *node = nf->refcount.rb_node; int cmp; lockdep_assert_held(&nf->lock); key.reg = reg; key.id = id; while (node) { entry = rb_entry(node, struct ssam_nf_refcount_entry, node); cmp = memcmp(&key, &entry->key, sizeof(key)); if (cmp < 0) { node = node->rb_left; } else if (cmp > 0) { node = node->rb_right; } else { entry->refcount--; if (entry->refcount == 0) rb_erase(&entry->node, &nf->refcount); return entry; } } return NULL;} /** * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the * given event and free its entry if the reference count reaches zero. * @nf: The notifier system reference. * @reg: The registry used to enable/disable the event. * @id: The event ID. * * Decrements the reference-/activation-count of the specified event, freeing * its entry if it reaches zero. * * Note: ``nf->lock`` must be held when calling this function. */static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, struct ssam_event_registry reg, struct ssam_event_id id){ struct ssam_nf_refcount_entry *entry; lockdep_assert_held(&nf->lock); entry = ssam_nf_refcount_dec(nf, reg, id); if (entry && entry->refcount == 0) kfree(entry);} /** * ssam_nf_refcount_empty() - Test if the notification system has any * enabled/active events. * @nf: The notification system. */static bool ssam_nf_refcount_empty(struct ssam_nf *nf){ return RB_EMPTY_ROOT(&nf->refcount);} /** * ssam_nf_call() - Call notification callbacks for the provided event. * @nf: The notifier system * @dev: The associated device, only used for logging. * @rqid: The request ID of the event. * @event: The event provided to the callbacks. * * Execute registered callbacks in order of their priority until either no * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP * bit set. Note that this bit is set automatically when converting non-zero * error values via ssam_notifier_from_errno() to notifier values. * * Also note that any callback that could handle an event should return a value * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go * unhandled/ignored. In case no registered callback could handle an event, * this function will emit a warning. * * In case a callback failed, this function will emit an error message. */static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid, struct ssam_event *event){ struct ssam_nf_head *nf_head; int status, nf_ret; if (!ssh_rqid_is_event(rqid)) { dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid); return; } nf_head = &nf->head[ssh_rqid_to_event(rqid)]; nf_ret = ssam_nfblk_call_chain(nf_head, event); status = ssam_notifier_to_errno(nf_ret); if (status < 0) { dev_err(dev, "event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", status, event->target_category, event->target_id, event->command_id, event->instance_id); } else if (!(nf_ret & SSAM_NOTIF_HANDLED)) { dev_warn(dev, "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", rqid, event->target_category, event->target_id, event->command_id, event->instance_id); }} /** * ssam_nf_init() - Initialize the notifier system. * @nf: The notifier system to initialize. */static int ssam_nf_init(struct ssam_nf *nf){ int i, status; for (i = 0; i < SSH_NUM_EVENTS; i++) {Lines 1–500 of 2847. Load more
// SPDX-License-Identifier: GPL-2.0+ /* * Main SSAM/SSH controller structure and functionality. * * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/acpi.h> #include <linux/atomic.h> #include <linux/completion.h> #include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/kref.h> #include <linux/limits.h> #include <linux/list.h> #include <linux/lockdep.h> #include <linux/mutex.h> #include <linux/rculist.h> #include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/serdev.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/srcu.h> #include <linux/types.h> #include <linux/workqueue.h> #include <linux/surface_aggregator/controller.h> #include <linux/surface_aggregator/serial_hub.h> #include "controller.h" #include "ssh_msgb.h" #include "ssh_request_layer.h" #include "trace.h" /* -- Safe counters. -------------------------------------------------------- */ /** * ssh_seq_reset() - Reset/initialize sequence ID counter. * @c: The counter to reset. */ static void ssh_seq_reset(struct ssh_seq_counter *c) { WRITE_ONCE(c->value, 0); } /** * ssh_seq_next() - Get next sequence ID. * @c: The counter providing the sequence IDs. * * Return: Returns the next sequence ID of the counter. */ static u8 ssh_seq_next(struct ssh_seq_counter *c) { u8 old = READ_ONCE(c->value); u8 new = old + 1; u8 ret; while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { old = ret; new = old + 1; } return old; } /** * ssh_rqid_reset() - Reset/initialize request ID counter. * @c: The counter to reset. */ static void ssh_rqid_reset(struct ssh_rqid_counter *c) { WRITE_ONCE(c->value, 0); } /** * ssh_rqid_next() - Get next request ID. * @c: The counter providing the request IDs. * * Return: Returns the next request ID of the counter, skipping any reserved * request IDs. */ static u16 ssh_rqid_next(struct ssh_rqid_counter *c) { u16 old = READ_ONCE(c->value); u16 new = ssh_rqid_next_valid(old); u16 ret; while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { old = ret; new = ssh_rqid_next_valid(old); } return old; } /* -- Event notifier/callbacks. --------------------------------------------- */ /* * The notifier system is based on linux/notifier.h, specifically the SRCU * implementation. The difference to that is, that some bits of the notifier * call return value can be tracked across multiple calls. This is done so * that handling of events can be tracked and a warning can be issued in case * an event goes unhandled. The idea of that warning is that it should help * discover and identify new/currently unimplemented features. */ /** * ssam_event_matches_notifier() - Test if an event matches a notifier. * @n: The event notifier to test against. * @event: The event to test. * * Return: Returns %true if the given event matches the given notifier * according to the rules set in the notifier's event mask, %false otherwise. */ static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n, const struct ssam_event *event) { bool match = n->event.id.target_category == event->target_category; if (n->event.mask & SSAM_EVENT_MASK_TARGET) match &= n->event.reg.target_id == event->target_id; if (n->event.mask & SSAM_EVENT_MASK_INSTANCE) match &= n->event.id.instance == event->instance_id; return match; } /** * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain. * @nh: The notifier head for which the notifier callbacks should be called. * @event: The event data provided to the callbacks. * * Call all registered notifier callbacks in order of their priority until * either no notifier is left or a notifier returns a value with the * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via * ssam_notifier_from_errno() on any non-zero error value. * * Return: Returns the notifier status value, which contains the notifier * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a * potential error value returned from the last executed notifier callback. * Use ssam_notifier_to_errno() to convert this value to the original error * value. */ static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event) { struct ssam_event_notifier *nf; int ret = 0, idx; idx = srcu_read_lock(&nh->srcu); list_for_each_entry_rcu(nf, &nh->head, base.node, srcu_read_lock_held(&nh->srcu)) { if (ssam_event_matches_notifier(nf, event)) { ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event); if (ret & SSAM_NOTIF_STOP) break; } } srcu_read_unlock(&nh->srcu, idx); return ret; } /** * ssam_nfblk_insert() - Insert a new notifier block into the given notifier * list. * @nh: The notifier head into which the block should be inserted. * @nb: The notifier block to add. * * Note: This function must be synchronized by the caller with respect to other * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. * * Return: Returns zero on success, %-EEXIST if the notifier block has already * been registered. */ static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) { struct ssam_notifier_block *p; struct list_head *h; /* Runs under lock, no need for RCU variant. */ list_for_each(h, &nh->head) { p = list_entry(h, struct ssam_notifier_block, node); if (unlikely(p == nb)) { WARN(1, "double register detected"); return -EEXIST; } if (nb->priority > p->priority) break; } list_add_tail_rcu(&nb->node, h); return 0; } /** * ssam_nfblk_find() - Check if a notifier block is registered on the given * notifier head. * list. * @nh: The notifier head on which to search. * @nb: The notifier block to search for. * * Note: This function must be synchronized by the caller with respect to other * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. * * Return: Returns true if the given notifier block is registered on the given * notifier head, false otherwise. */ static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) { struct ssam_notifier_block *p; /* Runs under lock, no need for RCU variant. */ list_for_each_entry(p, &nh->head, node) { if (p == nb) return true; } return false; } /** * ssam_nfblk_remove() - Remove a notifier block from its notifier list. * @nb: The notifier block to be removed. * * Note: This function must be synchronized by the caller with respect to * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. * Furthermore, the caller _must_ ensure SRCU synchronization by calling * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to * ensure that the removed notifier block is not in use any more. */ static void ssam_nfblk_remove(struct ssam_notifier_block *nb) { list_del_rcu(&nb->node); } /** * ssam_nf_head_init() - Initialize the given notifier head. * @nh: The notifier head to initialize. */ static int ssam_nf_head_init(struct ssam_nf_head *nh) { int status; status = init_srcu_struct(&nh->srcu); if (status) return status; INIT_LIST_HEAD(&nh->head); return 0; } /** * ssam_nf_head_destroy() - Deinitialize the given notifier head. * @nh: The notifier head to deinitialize. */ static void ssam_nf_head_destroy(struct ssam_nf_head *nh) { cleanup_srcu_struct(&nh->srcu); } /* -- Event/notification registry. ------------------------------------------ */ /** * struct ssam_nf_refcount_key - Key used for event activation reference * counting. * @reg: The registry via which the event is enabled/disabled. * @id: The ID uniquely describing the event. */ struct ssam_nf_refcount_key { struct ssam_event_registry reg; struct ssam_event_id id; }; /** * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event * activations. * @node: The node of this entry in the rb-tree. * @key: The key of the event. * @refcount: The reference-count of the event. * @flags: The flags used when enabling the event. */ struct ssam_nf_refcount_entry { struct rb_node node; struct ssam_nf_refcount_key key; int refcount; u8 flags; }; /** * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given * event. * @nf: The notifier system reference. * @reg: The registry used to enable/disable the event. * @id: The event ID. * * Increments the reference-/activation-count associated with the specified * event type/ID, allocating a new entry for this event ID if necessary. A * newly allocated entry will have a refcount of one. * * Note: ``nf->lock`` must be held when calling this function. * * Return: Returns the refcount entry on success. Returns an error pointer * with %-ENOSPC if there have already been %INT_MAX events of the specified * ID and type registered, or %-ENOMEM if the entry could not be allocated. */ static struct ssam_nf_refcount_entry * ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg, struct ssam_event_id id) { struct ssam_nf_refcount_entry *entry; struct ssam_nf_refcount_key key; struct rb_node **link = &nf->refcount.rb_node; struct rb_node *parent = NULL; int cmp; lockdep_assert_held(&nf->lock); key.reg = reg; key.id = id; while (*link) { entry = rb_entry(*link, struct ssam_nf_refcount_entry, node); parent = *link; cmp = memcmp(&key, &entry->key, sizeof(key)); if (cmp < 0) { link = &(*link)->rb_left; } else if (cmp > 0) { link = &(*link)->rb_right; } else if (entry->refcount < INT_MAX) { entry->refcount++; return entry; } else { WARN_ON(1); return ERR_PTR(-ENOSPC); } } entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return ERR_PTR(-ENOMEM); entry->key = key; entry->refcount = 1; rb_link_node(&entry->node, parent, link); rb_insert_color(&entry->node, &nf->refcount); return entry; } /** * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given * event. * @nf: The notifier system reference. * @reg: The registry used to enable/disable the event. * @id: The event ID. * * Decrements the reference-/activation-count of the specified event, * returning its entry. If the returned entry has a refcount of zero, the * caller is responsible for freeing it using kfree(). * * Note: ``nf->lock`` must be held when calling this function. * * Return: Returns the refcount entry on success or %NULL if the entry has not * been found. */ static struct ssam_nf_refcount_entry * ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, struct ssam_event_id id) { struct ssam_nf_refcount_entry *entry; struct ssam_nf_refcount_key key; struct rb_node *node = nf->refcount.rb_node; int cmp; lockdep_assert_held(&nf->lock); key.reg = reg; key.id = id; while (node) { entry = rb_entry(node, struct ssam_nf_refcount_entry, node); cmp = memcmp(&key, &entry->key, sizeof(key)); if (cmp < 0) { node = node->rb_left; } else if (cmp > 0) { node = node->rb_right; } else { entry->refcount--; if (entry->refcount == 0) rb_erase(&entry->node, &nf->refcount); return entry; } } return NULL; } /** * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the * given event and free its entry if the reference count reaches zero. * @nf: The notifier system reference. * @reg: The registry used to enable/disable the event. * @id: The event ID. * * Decrements the reference-/activation-count of the specified event, freeing * its entry if it reaches zero. * * Note: ``nf->lock`` must be held when calling this function. */ static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, struct ssam_event_registry reg, struct ssam_event_id id) { struct ssam_nf_refcount_entry *entry; lockdep_assert_held(&nf->lock); entry = ssam_nf_refcount_dec(nf, reg, id); if (entry && entry->refcount == 0) kfree(entry); } /** * ssam_nf_refcount_empty() - Test if the notification system has any * enabled/active events. * @nf: The notification system. */ static bool ssam_nf_refcount_empty(struct ssam_nf *nf) { return RB_EMPTY_ROOT(&nf->refcount); } /** * ssam_nf_call() - Call notification callbacks for the provided event. * @nf: The notifier system * @dev: The associated device, only used for logging. * @rqid: The request ID of the event. * @event: The event provided to the callbacks. * * Execute registered callbacks in order of their priority until either no * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP * bit set. Note that this bit is set automatically when converting non-zero * error values via ssam_notifier_from_errno() to notifier values. * * Also note that any callback that could handle an event should return a value * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go * unhandled/ignored. In case no registered callback could handle an event, * this function will emit a warning. * * In case a callback failed, this function will emit an error message. */ static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid, struct ssam_event *event) { struct ssam_nf_head *nf_head; int status, nf_ret; if (!ssh_rqid_is_event(rqid)) { dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid); return; } nf_head = &nf->head[ssh_rqid_to_event(rqid)]; nf_ret = ssam_nfblk_call_chain(nf_head, event); status = ssam_notifier_to_errno(nf_ret); if (status < 0) { dev_err(dev, "event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", status, event->target_category, event->target_id, event->command_id, event->instance_id); } else if (!(nf_ret & SSAM_NOTIF_HANDLED)) { dev_warn(dev, "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", rqid, event->target_category, event->target_id, event->command_id, event->instance_id); } } /** * ssam_nf_init() - Initialize the notifier system. * @nf: The notifier system to initialize. */ static int ssam_nf_init(struct ssam_nf *nf) { int i, status; for (i = 0; i < SSH_NUM_EVENTS; i++) {