162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Main SSAM/SSH controller structure and functionality. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/acpi.h> 962306a36Sopenharmony_ci#include <linux/atomic.h> 1062306a36Sopenharmony_ci#include <linux/completion.h> 1162306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1262306a36Sopenharmony_ci#include <linux/interrupt.h> 1362306a36Sopenharmony_ci#include <linux/kref.h> 1462306a36Sopenharmony_ci#include <linux/limits.h> 1562306a36Sopenharmony_ci#include <linux/list.h> 1662306a36Sopenharmony_ci#include <linux/lockdep.h> 1762306a36Sopenharmony_ci#include <linux/mutex.h> 1862306a36Sopenharmony_ci#include <linux/rculist.h> 1962306a36Sopenharmony_ci#include <linux/rbtree.h> 2062306a36Sopenharmony_ci#include <linux/rwsem.h> 2162306a36Sopenharmony_ci#include <linux/serdev.h> 2262306a36Sopenharmony_ci#include <linux/slab.h> 2362306a36Sopenharmony_ci#include <linux/spinlock.h> 2462306a36Sopenharmony_ci#include <linux/srcu.h> 2562306a36Sopenharmony_ci#include <linux/types.h> 2662306a36Sopenharmony_ci#include <linux/workqueue.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include <linux/surface_aggregator/controller.h> 2962306a36Sopenharmony_ci#include <linux/surface_aggregator/serial_hub.h> 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#include "controller.h" 3262306a36Sopenharmony_ci#include "ssh_msgb.h" 3362306a36Sopenharmony_ci#include "ssh_request_layer.h" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include "trace.h" 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* -- Safe counters. -------------------------------------------------------- */ 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/** 4162306a36Sopenharmony_ci * ssh_seq_reset() - Reset/initialize sequence ID counter. 4262306a36Sopenharmony_ci * @c: The counter to reset. 4362306a36Sopenharmony_ci */ 4462306a36Sopenharmony_cistatic void ssh_seq_reset(struct ssh_seq_counter *c) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci WRITE_ONCE(c->value, 0); 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci/** 5062306a36Sopenharmony_ci * ssh_seq_next() - Get next sequence ID. 5162306a36Sopenharmony_ci * @c: The counter providing the sequence IDs. 5262306a36Sopenharmony_ci * 5362306a36Sopenharmony_ci * Return: Returns the next sequence ID of the counter. 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_cistatic u8 ssh_seq_next(struct ssh_seq_counter *c) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci u8 old = READ_ONCE(c->value); 5862306a36Sopenharmony_ci u8 new = old + 1; 5962306a36Sopenharmony_ci u8 ret; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { 6262306a36Sopenharmony_ci old = ret; 6362306a36Sopenharmony_ci new = old + 1; 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci return old; 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/** 7062306a36Sopenharmony_ci * ssh_rqid_reset() - Reset/initialize request ID counter. 7162306a36Sopenharmony_ci * @c: The counter to reset. 7262306a36Sopenharmony_ci */ 7362306a36Sopenharmony_cistatic void ssh_rqid_reset(struct ssh_rqid_counter *c) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci WRITE_ONCE(c->value, 0); 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/** 7962306a36Sopenharmony_ci * ssh_rqid_next() - Get next request ID. 8062306a36Sopenharmony_ci * @c: The counter providing the request IDs. 8162306a36Sopenharmony_ci * 8262306a36Sopenharmony_ci * Return: Returns the next request ID of the counter, skipping any reserved 8362306a36Sopenharmony_ci * request IDs. 8462306a36Sopenharmony_ci */ 8562306a36Sopenharmony_cistatic u16 ssh_rqid_next(struct ssh_rqid_counter *c) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci u16 old = READ_ONCE(c->value); 8862306a36Sopenharmony_ci u16 new = ssh_rqid_next_valid(old); 8962306a36Sopenharmony_ci u16 ret; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { 9262306a36Sopenharmony_ci old = ret; 9362306a36Sopenharmony_ci new = ssh_rqid_next_valid(old); 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci return old; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/* -- Event notifier/callbacks. --------------------------------------------- */ 10162306a36Sopenharmony_ci/* 10262306a36Sopenharmony_ci * The notifier system is based on linux/notifier.h, specifically the SRCU 10362306a36Sopenharmony_ci * implementation. The difference to that is, that some bits of the notifier 10462306a36Sopenharmony_ci * call return value can be tracked across multiple calls. This is done so 10562306a36Sopenharmony_ci * that handling of events can be tracked and a warning can be issued in case 10662306a36Sopenharmony_ci * an event goes unhandled. The idea of that warning is that it should help 10762306a36Sopenharmony_ci * discover and identify new/currently unimplemented features. 10862306a36Sopenharmony_ci */ 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci/** 11162306a36Sopenharmony_ci * ssam_event_matches_notifier() - Test if an event matches a notifier. 11262306a36Sopenharmony_ci * @n: The event notifier to test against. 11362306a36Sopenharmony_ci * @event: The event to test. 11462306a36Sopenharmony_ci * 11562306a36Sopenharmony_ci * Return: Returns %true if the given event matches the given notifier 11662306a36Sopenharmony_ci * according to the rules set in the notifier's event mask, %false otherwise. 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_cistatic bool ssam_event_matches_notifier(const struct ssam_event_notifier *n, 11962306a36Sopenharmony_ci const struct ssam_event *event) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci bool match = n->event.id.target_category == event->target_category; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (n->event.mask & SSAM_EVENT_MASK_TARGET) 12462306a36Sopenharmony_ci match &= n->event.reg.target_id == event->target_id; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (n->event.mask & SSAM_EVENT_MASK_INSTANCE) 12762306a36Sopenharmony_ci match &= n->event.id.instance == event->instance_id; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci return match; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci/** 13362306a36Sopenharmony_ci * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain. 13462306a36Sopenharmony_ci * @nh: The notifier head for which the notifier callbacks should be called. 13562306a36Sopenharmony_ci * @event: The event data provided to the callbacks. 13662306a36Sopenharmony_ci * 13762306a36Sopenharmony_ci * Call all registered notifier callbacks in order of their priority until 13862306a36Sopenharmony_ci * either no notifier is left or a notifier returns a value with the 13962306a36Sopenharmony_ci * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via 14062306a36Sopenharmony_ci * ssam_notifier_from_errno() on any non-zero error value. 14162306a36Sopenharmony_ci * 14262306a36Sopenharmony_ci * Return: Returns the notifier status value, which contains the notifier 14362306a36Sopenharmony_ci * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a 14462306a36Sopenharmony_ci * potential error value returned from the last executed notifier callback. 14562306a36Sopenharmony_ci * Use ssam_notifier_to_errno() to convert this value to the original error 14662306a36Sopenharmony_ci * value. 14762306a36Sopenharmony_ci */ 14862306a36Sopenharmony_cistatic int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci struct ssam_event_notifier *nf; 15162306a36Sopenharmony_ci int ret = 0, idx; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci idx = srcu_read_lock(&nh->srcu); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci list_for_each_entry_rcu(nf, &nh->head, base.node, 15662306a36Sopenharmony_ci srcu_read_lock_held(&nh->srcu)) { 15762306a36Sopenharmony_ci if (ssam_event_matches_notifier(nf, event)) { 15862306a36Sopenharmony_ci ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event); 15962306a36Sopenharmony_ci if (ret & SSAM_NOTIF_STOP) 16062306a36Sopenharmony_ci break; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci srcu_read_unlock(&nh->srcu, idx); 16562306a36Sopenharmony_ci return ret; 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci/** 16962306a36Sopenharmony_ci * ssam_nfblk_insert() - Insert a new notifier block into the given notifier 17062306a36Sopenharmony_ci * list. 17162306a36Sopenharmony_ci * @nh: The notifier head into which the block should be inserted. 17262306a36Sopenharmony_ci * @nb: The notifier block to add. 17362306a36Sopenharmony_ci * 17462306a36Sopenharmony_ci * Note: This function must be synchronized by the caller with respect to other 17562306a36Sopenharmony_ci * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. 17662306a36Sopenharmony_ci * 17762306a36Sopenharmony_ci * Return: Returns zero on success, %-EEXIST if the notifier block has already 17862306a36Sopenharmony_ci * been registered. 17962306a36Sopenharmony_ci */ 18062306a36Sopenharmony_cistatic int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct ssam_notifier_block *p; 18362306a36Sopenharmony_ci struct list_head *h; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* Runs under lock, no need for RCU variant. */ 18662306a36Sopenharmony_ci list_for_each(h, &nh->head) { 18762306a36Sopenharmony_ci p = list_entry(h, struct ssam_notifier_block, node); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (unlikely(p == nb)) { 19062306a36Sopenharmony_ci WARN(1, "double register detected"); 19162306a36Sopenharmony_ci return -EEXIST; 19262306a36Sopenharmony_ci } 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (nb->priority > p->priority) 19562306a36Sopenharmony_ci break; 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci list_add_tail_rcu(&nb->node, h); 19962306a36Sopenharmony_ci return 0; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci/** 20362306a36Sopenharmony_ci * ssam_nfblk_find() - Check if a notifier block is registered on the given 20462306a36Sopenharmony_ci * notifier head. 20562306a36Sopenharmony_ci * list. 20662306a36Sopenharmony_ci * @nh: The notifier head on which to search. 20762306a36Sopenharmony_ci * @nb: The notifier block to search for. 20862306a36Sopenharmony_ci * 20962306a36Sopenharmony_ci * Note: This function must be synchronized by the caller with respect to other 21062306a36Sopenharmony_ci * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. 21162306a36Sopenharmony_ci * 21262306a36Sopenharmony_ci * Return: Returns true if the given notifier block is registered on the given 21362306a36Sopenharmony_ci * notifier head, false otherwise. 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_cistatic bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct ssam_notifier_block *p; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* Runs under lock, no need for RCU variant. */ 22062306a36Sopenharmony_ci list_for_each_entry(p, &nh->head, node) { 22162306a36Sopenharmony_ci if (p == nb) 22262306a36Sopenharmony_ci return true; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci return false; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci/** 22962306a36Sopenharmony_ci * ssam_nfblk_remove() - Remove a notifier block from its notifier list. 23062306a36Sopenharmony_ci * @nb: The notifier block to be removed. 23162306a36Sopenharmony_ci * 23262306a36Sopenharmony_ci * Note: This function must be synchronized by the caller with respect to 23362306a36Sopenharmony_ci * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. 23462306a36Sopenharmony_ci * Furthermore, the caller _must_ ensure SRCU synchronization by calling 23562306a36Sopenharmony_ci * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to 23662306a36Sopenharmony_ci * ensure that the removed notifier block is not in use any more. 23762306a36Sopenharmony_ci */ 23862306a36Sopenharmony_cistatic void ssam_nfblk_remove(struct ssam_notifier_block *nb) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci list_del_rcu(&nb->node); 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci/** 24462306a36Sopenharmony_ci * ssam_nf_head_init() - Initialize the given notifier head. 24562306a36Sopenharmony_ci * @nh: The notifier head to initialize. 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_cistatic int ssam_nf_head_init(struct ssam_nf_head *nh) 24862306a36Sopenharmony_ci{ 24962306a36Sopenharmony_ci int status; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci status = init_srcu_struct(&nh->srcu); 25262306a36Sopenharmony_ci if (status) 25362306a36Sopenharmony_ci return status; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci INIT_LIST_HEAD(&nh->head); 25662306a36Sopenharmony_ci return 0; 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci/** 26062306a36Sopenharmony_ci * ssam_nf_head_destroy() - Deinitialize the given notifier head. 26162306a36Sopenharmony_ci * @nh: The notifier head to deinitialize. 26262306a36Sopenharmony_ci */ 26362306a36Sopenharmony_cistatic void ssam_nf_head_destroy(struct ssam_nf_head *nh) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci cleanup_srcu_struct(&nh->srcu); 26662306a36Sopenharmony_ci} 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci/* -- Event/notification registry. ------------------------------------------ */ 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci/** 27262306a36Sopenharmony_ci * struct ssam_nf_refcount_key - Key used for event activation reference 27362306a36Sopenharmony_ci * counting. 27462306a36Sopenharmony_ci * @reg: The registry via which the event is enabled/disabled. 27562306a36Sopenharmony_ci * @id: The ID uniquely describing the event. 27662306a36Sopenharmony_ci */ 27762306a36Sopenharmony_cistruct ssam_nf_refcount_key { 27862306a36Sopenharmony_ci struct ssam_event_registry reg; 27962306a36Sopenharmony_ci struct ssam_event_id id; 28062306a36Sopenharmony_ci}; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci/** 28362306a36Sopenharmony_ci * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event 28462306a36Sopenharmony_ci * activations. 28562306a36Sopenharmony_ci * @node: The node of this entry in the rb-tree. 28662306a36Sopenharmony_ci * @key: The key of the event. 28762306a36Sopenharmony_ci * @refcount: The reference-count of the event. 28862306a36Sopenharmony_ci * @flags: The flags used when enabling the event. 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_cistruct ssam_nf_refcount_entry { 29162306a36Sopenharmony_ci struct rb_node node; 29262306a36Sopenharmony_ci struct ssam_nf_refcount_key key; 29362306a36Sopenharmony_ci int refcount; 29462306a36Sopenharmony_ci u8 flags; 29562306a36Sopenharmony_ci}; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci/** 29862306a36Sopenharmony_ci * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given 29962306a36Sopenharmony_ci * event. 30062306a36Sopenharmony_ci * @nf: The notifier system reference. 30162306a36Sopenharmony_ci * @reg: The registry used to enable/disable the event. 30262306a36Sopenharmony_ci * @id: The event ID. 30362306a36Sopenharmony_ci * 30462306a36Sopenharmony_ci * Increments the reference-/activation-count associated with the specified 30562306a36Sopenharmony_ci * event type/ID, allocating a new entry for this event ID if necessary. A 30662306a36Sopenharmony_ci * newly allocated entry will have a refcount of one. 30762306a36Sopenharmony_ci * 30862306a36Sopenharmony_ci * Note: ``nf->lock`` must be held when calling this function. 30962306a36Sopenharmony_ci * 31062306a36Sopenharmony_ci * Return: Returns the refcount entry on success. Returns an error pointer 31162306a36Sopenharmony_ci * with %-ENOSPC if there have already been %INT_MAX events of the specified 31262306a36Sopenharmony_ci * ID and type registered, or %-ENOMEM if the entry could not be allocated. 31362306a36Sopenharmony_ci */ 31462306a36Sopenharmony_cistatic struct ssam_nf_refcount_entry * 31562306a36Sopenharmony_cissam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg, 31662306a36Sopenharmony_ci struct ssam_event_id id) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry; 31962306a36Sopenharmony_ci struct ssam_nf_refcount_key key; 32062306a36Sopenharmony_ci struct rb_node **link = &nf->refcount.rb_node; 32162306a36Sopenharmony_ci struct rb_node *parent = NULL; 32262306a36Sopenharmony_ci int cmp; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci lockdep_assert_held(&nf->lock); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci key.reg = reg; 32762306a36Sopenharmony_ci key.id = id; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci while (*link) { 33062306a36Sopenharmony_ci entry = rb_entry(*link, struct ssam_nf_refcount_entry, node); 33162306a36Sopenharmony_ci parent = *link; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci cmp = memcmp(&key, &entry->key, sizeof(key)); 33462306a36Sopenharmony_ci if (cmp < 0) { 33562306a36Sopenharmony_ci link = &(*link)->rb_left; 33662306a36Sopenharmony_ci } else if (cmp > 0) { 33762306a36Sopenharmony_ci link = &(*link)->rb_right; 33862306a36Sopenharmony_ci } else if (entry->refcount < INT_MAX) { 33962306a36Sopenharmony_ci entry->refcount++; 34062306a36Sopenharmony_ci return entry; 34162306a36Sopenharmony_ci } else { 34262306a36Sopenharmony_ci WARN_ON(1); 34362306a36Sopenharmony_ci return ERR_PTR(-ENOSPC); 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci entry = kzalloc(sizeof(*entry), GFP_KERNEL); 34862306a36Sopenharmony_ci if (!entry) 34962306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci entry->key = key; 35262306a36Sopenharmony_ci entry->refcount = 1; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci rb_link_node(&entry->node, parent, link); 35562306a36Sopenharmony_ci rb_insert_color(&entry->node, &nf->refcount); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci return entry; 35862306a36Sopenharmony_ci} 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci/** 36162306a36Sopenharmony_ci * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given 36262306a36Sopenharmony_ci * event. 36362306a36Sopenharmony_ci * @nf: The notifier system reference. 36462306a36Sopenharmony_ci * @reg: The registry used to enable/disable the event. 36562306a36Sopenharmony_ci * @id: The event ID. 36662306a36Sopenharmony_ci * 36762306a36Sopenharmony_ci * Decrements the reference-/activation-count of the specified event, 36862306a36Sopenharmony_ci * returning its entry. If the returned entry has a refcount of zero, the 36962306a36Sopenharmony_ci * caller is responsible for freeing it using kfree(). 37062306a36Sopenharmony_ci * 37162306a36Sopenharmony_ci * Note: ``nf->lock`` must be held when calling this function. 37262306a36Sopenharmony_ci * 37362306a36Sopenharmony_ci * Return: Returns the refcount entry on success or %NULL if the entry has not 37462306a36Sopenharmony_ci * been found. 37562306a36Sopenharmony_ci */ 37662306a36Sopenharmony_cistatic struct ssam_nf_refcount_entry * 37762306a36Sopenharmony_cissam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, 37862306a36Sopenharmony_ci struct ssam_event_id id) 37962306a36Sopenharmony_ci{ 38062306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry; 38162306a36Sopenharmony_ci struct ssam_nf_refcount_key key; 38262306a36Sopenharmony_ci struct rb_node *node = nf->refcount.rb_node; 38362306a36Sopenharmony_ci int cmp; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci lockdep_assert_held(&nf->lock); 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci key.reg = reg; 38862306a36Sopenharmony_ci key.id = id; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci while (node) { 39162306a36Sopenharmony_ci entry = rb_entry(node, struct ssam_nf_refcount_entry, node); 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci cmp = memcmp(&key, &entry->key, sizeof(key)); 39462306a36Sopenharmony_ci if (cmp < 0) { 39562306a36Sopenharmony_ci node = node->rb_left; 39662306a36Sopenharmony_ci } else if (cmp > 0) { 39762306a36Sopenharmony_ci node = node->rb_right; 39862306a36Sopenharmony_ci } else { 39962306a36Sopenharmony_ci entry->refcount--; 40062306a36Sopenharmony_ci if (entry->refcount == 0) 40162306a36Sopenharmony_ci rb_erase(&entry->node, &nf->refcount); 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci return entry; 40462306a36Sopenharmony_ci } 40562306a36Sopenharmony_ci } 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci return NULL; 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci/** 41162306a36Sopenharmony_ci * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the 41262306a36Sopenharmony_ci * given event and free its entry if the reference count reaches zero. 41362306a36Sopenharmony_ci * @nf: The notifier system reference. 41462306a36Sopenharmony_ci * @reg: The registry used to enable/disable the event. 41562306a36Sopenharmony_ci * @id: The event ID. 41662306a36Sopenharmony_ci * 41762306a36Sopenharmony_ci * Decrements the reference-/activation-count of the specified event, freeing 41862306a36Sopenharmony_ci * its entry if it reaches zero. 41962306a36Sopenharmony_ci * 42062306a36Sopenharmony_ci * Note: ``nf->lock`` must be held when calling this function. 42162306a36Sopenharmony_ci */ 42262306a36Sopenharmony_cistatic void ssam_nf_refcount_dec_free(struct ssam_nf *nf, 42362306a36Sopenharmony_ci struct ssam_event_registry reg, 42462306a36Sopenharmony_ci struct ssam_event_id id) 42562306a36Sopenharmony_ci{ 42662306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci lockdep_assert_held(&nf->lock); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci entry = ssam_nf_refcount_dec(nf, reg, id); 43162306a36Sopenharmony_ci if (entry && entry->refcount == 0) 43262306a36Sopenharmony_ci kfree(entry); 43362306a36Sopenharmony_ci} 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci/** 43662306a36Sopenharmony_ci * ssam_nf_refcount_empty() - Test if the notification system has any 43762306a36Sopenharmony_ci * enabled/active events. 43862306a36Sopenharmony_ci * @nf: The notification system. 43962306a36Sopenharmony_ci */ 44062306a36Sopenharmony_cistatic bool ssam_nf_refcount_empty(struct ssam_nf *nf) 44162306a36Sopenharmony_ci{ 44262306a36Sopenharmony_ci return RB_EMPTY_ROOT(&nf->refcount); 44362306a36Sopenharmony_ci} 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci/** 44662306a36Sopenharmony_ci * ssam_nf_call() - Call notification callbacks for the provided event. 44762306a36Sopenharmony_ci * @nf: The notifier system 44862306a36Sopenharmony_ci * @dev: The associated device, only used for logging. 44962306a36Sopenharmony_ci * @rqid: The request ID of the event. 45062306a36Sopenharmony_ci * @event: The event provided to the callbacks. 45162306a36Sopenharmony_ci * 45262306a36Sopenharmony_ci * Execute registered callbacks in order of their priority until either no 45362306a36Sopenharmony_ci * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP 45462306a36Sopenharmony_ci * bit set. Note that this bit is set automatically when converting non-zero 45562306a36Sopenharmony_ci * error values via ssam_notifier_from_errno() to notifier values. 45662306a36Sopenharmony_ci * 45762306a36Sopenharmony_ci * Also note that any callback that could handle an event should return a value 45862306a36Sopenharmony_ci * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go 45962306a36Sopenharmony_ci * unhandled/ignored. In case no registered callback could handle an event, 46062306a36Sopenharmony_ci * this function will emit a warning. 46162306a36Sopenharmony_ci * 46262306a36Sopenharmony_ci * In case a callback failed, this function will emit an error message. 46362306a36Sopenharmony_ci */ 46462306a36Sopenharmony_cistatic void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid, 46562306a36Sopenharmony_ci struct ssam_event *event) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci struct ssam_nf_head *nf_head; 46862306a36Sopenharmony_ci int status, nf_ret; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci if (!ssh_rqid_is_event(rqid)) { 47162306a36Sopenharmony_ci dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid); 47262306a36Sopenharmony_ci return; 47362306a36Sopenharmony_ci } 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci nf_head = &nf->head[ssh_rqid_to_event(rqid)]; 47662306a36Sopenharmony_ci nf_ret = ssam_nfblk_call_chain(nf_head, event); 47762306a36Sopenharmony_ci status = ssam_notifier_to_errno(nf_ret); 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci if (status < 0) { 48062306a36Sopenharmony_ci dev_err(dev, 48162306a36Sopenharmony_ci "event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", 48262306a36Sopenharmony_ci status, event->target_category, event->target_id, 48362306a36Sopenharmony_ci event->command_id, event->instance_id); 48462306a36Sopenharmony_ci } else if (!(nf_ret & SSAM_NOTIF_HANDLED)) { 48562306a36Sopenharmony_ci dev_warn(dev, 48662306a36Sopenharmony_ci "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", 48762306a36Sopenharmony_ci rqid, event->target_category, event->target_id, 48862306a36Sopenharmony_ci event->command_id, event->instance_id); 48962306a36Sopenharmony_ci } 49062306a36Sopenharmony_ci} 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci/** 49362306a36Sopenharmony_ci * ssam_nf_init() - Initialize the notifier system. 49462306a36Sopenharmony_ci * @nf: The notifier system to initialize. 49562306a36Sopenharmony_ci */ 49662306a36Sopenharmony_cistatic int ssam_nf_init(struct ssam_nf *nf) 49762306a36Sopenharmony_ci{ 49862306a36Sopenharmony_ci int i, status; 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci for (i = 0; i < SSH_NUM_EVENTS; i++) { 50162306a36Sopenharmony_ci status = ssam_nf_head_init(&nf->head[i]); 50262306a36Sopenharmony_ci if (status) 50362306a36Sopenharmony_ci break; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci if (status) { 50762306a36Sopenharmony_ci while (i--) 50862306a36Sopenharmony_ci ssam_nf_head_destroy(&nf->head[i]); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci return status; 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci mutex_init(&nf->lock); 51462306a36Sopenharmony_ci return 0; 51562306a36Sopenharmony_ci} 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci/** 51862306a36Sopenharmony_ci * ssam_nf_destroy() - Deinitialize the notifier system. 51962306a36Sopenharmony_ci * @nf: The notifier system to deinitialize. 52062306a36Sopenharmony_ci */ 52162306a36Sopenharmony_cistatic void ssam_nf_destroy(struct ssam_nf *nf) 52262306a36Sopenharmony_ci{ 52362306a36Sopenharmony_ci int i; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci for (i = 0; i < SSH_NUM_EVENTS; i++) 52662306a36Sopenharmony_ci ssam_nf_head_destroy(&nf->head[i]); 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci mutex_destroy(&nf->lock); 52962306a36Sopenharmony_ci} 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci/* -- Event/async request completion system. -------------------------------- */ 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci#define SSAM_CPLT_WQ_NAME "ssam_cpltq" 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci/* 53762306a36Sopenharmony_ci * SSAM_CPLT_WQ_BATCH - Maximum number of event item completions executed per 53862306a36Sopenharmony_ci * work execution. Used to prevent livelocking of the workqueue. Value chosen 53962306a36Sopenharmony_ci * via educated guess, may be adjusted. 54062306a36Sopenharmony_ci */ 54162306a36Sopenharmony_ci#define SSAM_CPLT_WQ_BATCH 10 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci/* 54462306a36Sopenharmony_ci * SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN - Maximum payload length for a cached 54562306a36Sopenharmony_ci * &struct ssam_event_item. 54662306a36Sopenharmony_ci * 54762306a36Sopenharmony_ci * This length has been chosen to be accommodate standard touchpad and 54862306a36Sopenharmony_ci * keyboard input events. Events with larger payloads will be allocated 54962306a36Sopenharmony_ci * separately. 55062306a36Sopenharmony_ci */ 55162306a36Sopenharmony_ci#define SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN 32 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_cistatic struct kmem_cache *ssam_event_item_cache; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci/** 55662306a36Sopenharmony_ci * ssam_event_item_cache_init() - Initialize the event item cache. 55762306a36Sopenharmony_ci */ 55862306a36Sopenharmony_ciint ssam_event_item_cache_init(void) 55962306a36Sopenharmony_ci{ 56062306a36Sopenharmony_ci const unsigned int size = sizeof(struct ssam_event_item) 56162306a36Sopenharmony_ci + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN; 56262306a36Sopenharmony_ci const unsigned int align = __alignof__(struct ssam_event_item); 56362306a36Sopenharmony_ci struct kmem_cache *cache; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci cache = kmem_cache_create("ssam_event_item", size, align, 0, NULL); 56662306a36Sopenharmony_ci if (!cache) 56762306a36Sopenharmony_ci return -ENOMEM; 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci ssam_event_item_cache = cache; 57062306a36Sopenharmony_ci return 0; 57162306a36Sopenharmony_ci} 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci/** 57462306a36Sopenharmony_ci * ssam_event_item_cache_destroy() - Deinitialize the event item cache. 57562306a36Sopenharmony_ci */ 57662306a36Sopenharmony_civoid ssam_event_item_cache_destroy(void) 57762306a36Sopenharmony_ci{ 57862306a36Sopenharmony_ci kmem_cache_destroy(ssam_event_item_cache); 57962306a36Sopenharmony_ci ssam_event_item_cache = NULL; 58062306a36Sopenharmony_ci} 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_cistatic void __ssam_event_item_free_cached(struct ssam_event_item *item) 58362306a36Sopenharmony_ci{ 58462306a36Sopenharmony_ci kmem_cache_free(ssam_event_item_cache, item); 58562306a36Sopenharmony_ci} 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_cistatic void __ssam_event_item_free_generic(struct ssam_event_item *item) 58862306a36Sopenharmony_ci{ 58962306a36Sopenharmony_ci kfree(item); 59062306a36Sopenharmony_ci} 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci/** 59362306a36Sopenharmony_ci * ssam_event_item_free() - Free the provided event item. 59462306a36Sopenharmony_ci * @item: The event item to free. 59562306a36Sopenharmony_ci */ 59662306a36Sopenharmony_cistatic void ssam_event_item_free(struct ssam_event_item *item) 59762306a36Sopenharmony_ci{ 59862306a36Sopenharmony_ci trace_ssam_event_item_free(item); 59962306a36Sopenharmony_ci item->ops.free(item); 60062306a36Sopenharmony_ci} 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci/** 60362306a36Sopenharmony_ci * ssam_event_item_alloc() - Allocate an event item with the given payload size. 60462306a36Sopenharmony_ci * @len: The event payload length. 60562306a36Sopenharmony_ci * @flags: The flags used for allocation. 60662306a36Sopenharmony_ci * 60762306a36Sopenharmony_ci * Allocate an event item with the given payload size, preferring allocation 60862306a36Sopenharmony_ci * from the event item cache if the payload is small enough (i.e. smaller than 60962306a36Sopenharmony_ci * %SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN). Sets the item operations and payload 61062306a36Sopenharmony_ci * length values. The item free callback (``ops.free``) should not be 61162306a36Sopenharmony_ci * overwritten after this call. 61262306a36Sopenharmony_ci * 61362306a36Sopenharmony_ci * Return: Returns the newly allocated event item. 61462306a36Sopenharmony_ci */ 61562306a36Sopenharmony_cistatic struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) 61662306a36Sopenharmony_ci{ 61762306a36Sopenharmony_ci struct ssam_event_item *item; 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci if (len <= SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN) { 62062306a36Sopenharmony_ci item = kmem_cache_alloc(ssam_event_item_cache, flags); 62162306a36Sopenharmony_ci if (!item) 62262306a36Sopenharmony_ci return NULL; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci item->ops.free = __ssam_event_item_free_cached; 62562306a36Sopenharmony_ci } else { 62662306a36Sopenharmony_ci item = kzalloc(struct_size(item, event.data, len), flags); 62762306a36Sopenharmony_ci if (!item) 62862306a36Sopenharmony_ci return NULL; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci item->ops.free = __ssam_event_item_free_generic; 63162306a36Sopenharmony_ci } 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci item->event.length = len; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci trace_ssam_event_item_alloc(item, len); 63662306a36Sopenharmony_ci return item; 63762306a36Sopenharmony_ci} 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci/** 64062306a36Sopenharmony_ci * ssam_event_queue_push() - Push an event item to the event queue. 64162306a36Sopenharmony_ci * @q: The event queue. 64262306a36Sopenharmony_ci * @item: The item to add. 64362306a36Sopenharmony_ci */ 64462306a36Sopenharmony_cistatic void ssam_event_queue_push(struct ssam_event_queue *q, 64562306a36Sopenharmony_ci struct ssam_event_item *item) 64662306a36Sopenharmony_ci{ 64762306a36Sopenharmony_ci spin_lock(&q->lock); 64862306a36Sopenharmony_ci list_add_tail(&item->node, &q->head); 64962306a36Sopenharmony_ci spin_unlock(&q->lock); 65062306a36Sopenharmony_ci} 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci/** 65362306a36Sopenharmony_ci * ssam_event_queue_pop() - Pop the next event item from the event queue. 65462306a36Sopenharmony_ci * @q: The event queue. 65562306a36Sopenharmony_ci * 65662306a36Sopenharmony_ci * Returns and removes the next event item from the queue. Returns %NULL If 65762306a36Sopenharmony_ci * there is no event item left. 65862306a36Sopenharmony_ci */ 65962306a36Sopenharmony_cistatic struct ssam_event_item *ssam_event_queue_pop(struct ssam_event_queue *q) 66062306a36Sopenharmony_ci{ 66162306a36Sopenharmony_ci struct ssam_event_item *item; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci spin_lock(&q->lock); 66462306a36Sopenharmony_ci item = list_first_entry_or_null(&q->head, struct ssam_event_item, node); 66562306a36Sopenharmony_ci if (item) 66662306a36Sopenharmony_ci list_del(&item->node); 66762306a36Sopenharmony_ci spin_unlock(&q->lock); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci return item; 67062306a36Sopenharmony_ci} 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci/** 67362306a36Sopenharmony_ci * ssam_event_queue_is_empty() - Check if the event queue is empty. 67462306a36Sopenharmony_ci * @q: The event queue. 67562306a36Sopenharmony_ci */ 67662306a36Sopenharmony_cistatic bool ssam_event_queue_is_empty(struct ssam_event_queue *q) 67762306a36Sopenharmony_ci{ 67862306a36Sopenharmony_ci bool empty; 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci spin_lock(&q->lock); 68162306a36Sopenharmony_ci empty = list_empty(&q->head); 68262306a36Sopenharmony_ci spin_unlock(&q->lock); 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci return empty; 68562306a36Sopenharmony_ci} 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci/** 68862306a36Sopenharmony_ci * ssam_cplt_get_event_queue() - Get the event queue for the given parameters. 68962306a36Sopenharmony_ci * @cplt: The completion system on which to look for the queue. 69062306a36Sopenharmony_ci * @tid: The target ID of the queue. 69162306a36Sopenharmony_ci * @rqid: The request ID representing the event ID for which to get the queue. 69262306a36Sopenharmony_ci * 69362306a36Sopenharmony_ci * Return: Returns the event queue corresponding to the event type described 69462306a36Sopenharmony_ci * by the given parameters. If the request ID does not represent an event, 69562306a36Sopenharmony_ci * this function returns %NULL. If the target ID is not supported, this 69662306a36Sopenharmony_ci * function will fall back to the default target ID (``tid = 1``). 69762306a36Sopenharmony_ci */ 69862306a36Sopenharmony_cistatic 69962306a36Sopenharmony_cistruct ssam_event_queue *ssam_cplt_get_event_queue(struct ssam_cplt *cplt, 70062306a36Sopenharmony_ci u8 tid, u16 rqid) 70162306a36Sopenharmony_ci{ 70262306a36Sopenharmony_ci u16 event = ssh_rqid_to_event(rqid); 70362306a36Sopenharmony_ci u16 tidx = ssh_tid_to_index(tid); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci if (!ssh_rqid_is_event(rqid)) { 70662306a36Sopenharmony_ci dev_err(cplt->dev, "event: unsupported request ID: %#06x\n", rqid); 70762306a36Sopenharmony_ci return NULL; 70862306a36Sopenharmony_ci } 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci if (!ssh_tid_is_valid(tid)) { 71162306a36Sopenharmony_ci dev_warn(cplt->dev, "event: unsupported target ID: %u\n", tid); 71262306a36Sopenharmony_ci tidx = 0; 71362306a36Sopenharmony_ci } 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci return &cplt->event.target[tidx].queue[event]; 71662306a36Sopenharmony_ci} 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci/** 71962306a36Sopenharmony_ci * ssam_cplt_submit() - Submit a work item to the completion system workqueue. 72062306a36Sopenharmony_ci * @cplt: The completion system. 72162306a36Sopenharmony_ci * @work: The work item to submit. 72262306a36Sopenharmony_ci */ 72362306a36Sopenharmony_cistatic bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work) 72462306a36Sopenharmony_ci{ 72562306a36Sopenharmony_ci return queue_work(cplt->wq, work); 72662306a36Sopenharmony_ci} 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci/** 72962306a36Sopenharmony_ci * ssam_cplt_submit_event() - Submit an event to the completion system. 73062306a36Sopenharmony_ci * @cplt: The completion system. 73162306a36Sopenharmony_ci * @item: The event item to submit. 73262306a36Sopenharmony_ci * 73362306a36Sopenharmony_ci * Submits the event to the completion system by queuing it on the event item 73462306a36Sopenharmony_ci * queue and queuing the respective event queue work item on the completion 73562306a36Sopenharmony_ci * workqueue, which will eventually complete the event. 73662306a36Sopenharmony_ci * 73762306a36Sopenharmony_ci * Return: Returns zero on success, %-EINVAL if there is no event queue that 73862306a36Sopenharmony_ci * can handle the given event item. 73962306a36Sopenharmony_ci */ 74062306a36Sopenharmony_cistatic int ssam_cplt_submit_event(struct ssam_cplt *cplt, 74162306a36Sopenharmony_ci struct ssam_event_item *item) 74262306a36Sopenharmony_ci{ 74362306a36Sopenharmony_ci struct ssam_event_queue *evq; 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci evq = ssam_cplt_get_event_queue(cplt, item->event.target_id, item->rqid); 74662306a36Sopenharmony_ci if (!evq) 74762306a36Sopenharmony_ci return -EINVAL; 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_ci ssam_event_queue_push(evq, item); 75062306a36Sopenharmony_ci ssam_cplt_submit(cplt, &evq->work); 75162306a36Sopenharmony_ci return 0; 75262306a36Sopenharmony_ci} 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci/** 75562306a36Sopenharmony_ci * ssam_cplt_flush() - Flush the completion system. 75662306a36Sopenharmony_ci * @cplt: The completion system. 75762306a36Sopenharmony_ci * 75862306a36Sopenharmony_ci * Flush the completion system by waiting until all currently submitted work 75962306a36Sopenharmony_ci * items have been completed. 76062306a36Sopenharmony_ci * 76162306a36Sopenharmony_ci * Note: This function does not guarantee that all events will have been 76262306a36Sopenharmony_ci * handled once this call terminates. In case of a larger number of 76362306a36Sopenharmony_ci * to-be-completed events, the event queue work function may re-schedule its 76462306a36Sopenharmony_ci * work item, which this flush operation will ignore. 76562306a36Sopenharmony_ci * 76662306a36Sopenharmony_ci * This operation is only intended to, during normal operation prior to 76762306a36Sopenharmony_ci * shutdown, try to complete most events and requests to get them out of the 76862306a36Sopenharmony_ci * system while the system is still fully operational. It does not aim to 76962306a36Sopenharmony_ci * provide any guarantee that all of them have been handled. 77062306a36Sopenharmony_ci */ 77162306a36Sopenharmony_cistatic void ssam_cplt_flush(struct ssam_cplt *cplt) 77262306a36Sopenharmony_ci{ 77362306a36Sopenharmony_ci flush_workqueue(cplt->wq); 77462306a36Sopenharmony_ci} 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_cistatic void ssam_event_queue_work_fn(struct work_struct *work) 77762306a36Sopenharmony_ci{ 77862306a36Sopenharmony_ci struct ssam_event_queue *queue; 77962306a36Sopenharmony_ci struct ssam_event_item *item; 78062306a36Sopenharmony_ci struct ssam_nf *nf; 78162306a36Sopenharmony_ci struct device *dev; 78262306a36Sopenharmony_ci unsigned int iterations = SSAM_CPLT_WQ_BATCH; 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci queue = container_of(work, struct ssam_event_queue, work); 78562306a36Sopenharmony_ci nf = &queue->cplt->event.notif; 78662306a36Sopenharmony_ci dev = queue->cplt->dev; 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_ci /* Limit number of processed events to avoid livelocking. */ 78962306a36Sopenharmony_ci do { 79062306a36Sopenharmony_ci item = ssam_event_queue_pop(queue); 79162306a36Sopenharmony_ci if (!item) 79262306a36Sopenharmony_ci return; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci ssam_nf_call(nf, dev, item->rqid, &item->event); 79562306a36Sopenharmony_ci ssam_event_item_free(item); 79662306a36Sopenharmony_ci } while (--iterations); 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci if (!ssam_event_queue_is_empty(queue)) 79962306a36Sopenharmony_ci ssam_cplt_submit(queue->cplt, &queue->work); 80062306a36Sopenharmony_ci} 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci/** 80362306a36Sopenharmony_ci * ssam_event_queue_init() - Initialize an event queue. 80462306a36Sopenharmony_ci * @cplt: The completion system on which the queue resides. 80562306a36Sopenharmony_ci * @evq: The event queue to initialize. 80662306a36Sopenharmony_ci */ 80762306a36Sopenharmony_cistatic void ssam_event_queue_init(struct ssam_cplt *cplt, 80862306a36Sopenharmony_ci struct ssam_event_queue *evq) 80962306a36Sopenharmony_ci{ 81062306a36Sopenharmony_ci evq->cplt = cplt; 81162306a36Sopenharmony_ci spin_lock_init(&evq->lock); 81262306a36Sopenharmony_ci INIT_LIST_HEAD(&evq->head); 81362306a36Sopenharmony_ci INIT_WORK(&evq->work, ssam_event_queue_work_fn); 81462306a36Sopenharmony_ci} 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci/** 81762306a36Sopenharmony_ci * ssam_cplt_init() - Initialize completion system. 81862306a36Sopenharmony_ci * @cplt: The completion system to initialize. 81962306a36Sopenharmony_ci * @dev: The device used for logging. 82062306a36Sopenharmony_ci */ 82162306a36Sopenharmony_cistatic int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev) 82262306a36Sopenharmony_ci{ 82362306a36Sopenharmony_ci struct ssam_event_target *target; 82462306a36Sopenharmony_ci int status, c, i; 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ci cplt->dev = dev; 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci cplt->wq = alloc_workqueue(SSAM_CPLT_WQ_NAME, WQ_UNBOUND | WQ_MEM_RECLAIM, 0); 82962306a36Sopenharmony_ci if (!cplt->wq) 83062306a36Sopenharmony_ci return -ENOMEM; 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci for (c = 0; c < ARRAY_SIZE(cplt->event.target); c++) { 83362306a36Sopenharmony_ci target = &cplt->event.target[c]; 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(target->queue); i++) 83662306a36Sopenharmony_ci ssam_event_queue_init(cplt, &target->queue[i]); 83762306a36Sopenharmony_ci } 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_ci status = ssam_nf_init(&cplt->event.notif); 84062306a36Sopenharmony_ci if (status) 84162306a36Sopenharmony_ci destroy_workqueue(cplt->wq); 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci return status; 84462306a36Sopenharmony_ci} 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci/** 84762306a36Sopenharmony_ci * ssam_cplt_destroy() - Deinitialize the completion system. 84862306a36Sopenharmony_ci * @cplt: The completion system to deinitialize. 84962306a36Sopenharmony_ci * 85062306a36Sopenharmony_ci * Deinitialize the given completion system and ensure that all pending, i.e. 85162306a36Sopenharmony_ci * yet-to-be-completed, event items and requests have been handled. 85262306a36Sopenharmony_ci */ 85362306a36Sopenharmony_cistatic void ssam_cplt_destroy(struct ssam_cplt *cplt) 85462306a36Sopenharmony_ci{ 85562306a36Sopenharmony_ci /* 85662306a36Sopenharmony_ci * Note: destroy_workqueue ensures that all currently queued work will 85762306a36Sopenharmony_ci * be fully completed and the workqueue drained. This means that this 85862306a36Sopenharmony_ci * call will inherently also free any queued ssam_event_items, thus we 85962306a36Sopenharmony_ci * don't have to take care of that here explicitly. 86062306a36Sopenharmony_ci */ 86162306a36Sopenharmony_ci destroy_workqueue(cplt->wq); 86262306a36Sopenharmony_ci ssam_nf_destroy(&cplt->event.notif); 86362306a36Sopenharmony_ci} 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci/* -- Main SSAM device structures. ------------------------------------------ */ 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci/** 86962306a36Sopenharmony_ci * ssam_controller_device() - Get the &struct device associated with this 87062306a36Sopenharmony_ci * controller. 87162306a36Sopenharmony_ci * @c: The controller for which to get the device. 87262306a36Sopenharmony_ci * 87362306a36Sopenharmony_ci * Return: Returns the &struct device associated with this controller, 87462306a36Sopenharmony_ci * providing its lower-level transport. 87562306a36Sopenharmony_ci */ 87662306a36Sopenharmony_cistruct device *ssam_controller_device(struct ssam_controller *c) 87762306a36Sopenharmony_ci{ 87862306a36Sopenharmony_ci return ssh_rtl_get_device(&c->rtl); 87962306a36Sopenharmony_ci} 88062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_controller_device); 88162306a36Sopenharmony_ci 88262306a36Sopenharmony_cistatic void __ssam_controller_release(struct kref *kref) 88362306a36Sopenharmony_ci{ 88462306a36Sopenharmony_ci struct ssam_controller *ctrl = to_ssam_controller(kref, kref); 88562306a36Sopenharmony_ci 88662306a36Sopenharmony_ci /* 88762306a36Sopenharmony_ci * The lock-call here is to satisfy lockdep. At this point we really 88862306a36Sopenharmony_ci * expect this to be the last remaining reference to the controller. 88962306a36Sopenharmony_ci * Anything else is a bug. 89062306a36Sopenharmony_ci */ 89162306a36Sopenharmony_ci ssam_controller_lock(ctrl); 89262306a36Sopenharmony_ci ssam_controller_destroy(ctrl); 89362306a36Sopenharmony_ci ssam_controller_unlock(ctrl); 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci kfree(ctrl); 89662306a36Sopenharmony_ci} 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci/** 89962306a36Sopenharmony_ci * ssam_controller_get() - Increment reference count of controller. 90062306a36Sopenharmony_ci * @c: The controller. 90162306a36Sopenharmony_ci * 90262306a36Sopenharmony_ci * Return: Returns the controller provided as input. 90362306a36Sopenharmony_ci */ 90462306a36Sopenharmony_cistruct ssam_controller *ssam_controller_get(struct ssam_controller *c) 90562306a36Sopenharmony_ci{ 90662306a36Sopenharmony_ci if (c) 90762306a36Sopenharmony_ci kref_get(&c->kref); 90862306a36Sopenharmony_ci return c; 90962306a36Sopenharmony_ci} 91062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_controller_get); 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_ci/** 91362306a36Sopenharmony_ci * ssam_controller_put() - Decrement reference count of controller. 91462306a36Sopenharmony_ci * @c: The controller. 91562306a36Sopenharmony_ci */ 91662306a36Sopenharmony_civoid ssam_controller_put(struct ssam_controller *c) 91762306a36Sopenharmony_ci{ 91862306a36Sopenharmony_ci if (c) 91962306a36Sopenharmony_ci kref_put(&c->kref, __ssam_controller_release); 92062306a36Sopenharmony_ci} 92162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_controller_put); 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_ci/** 92462306a36Sopenharmony_ci * ssam_controller_statelock() - Lock the controller against state transitions. 92562306a36Sopenharmony_ci * @c: The controller to lock. 92662306a36Sopenharmony_ci * 92762306a36Sopenharmony_ci * Lock the controller against state transitions. Holding this lock guarantees 92862306a36Sopenharmony_ci * that the controller will not transition between states, i.e. if the 92962306a36Sopenharmony_ci * controller is in state "started", when this lock has been acquired, it will 93062306a36Sopenharmony_ci * remain in this state at least until the lock has been released. 93162306a36Sopenharmony_ci * 93262306a36Sopenharmony_ci * Multiple clients may concurrently hold this lock. In other words: The 93362306a36Sopenharmony_ci * ``statelock`` functions represent the read-lock part of a r/w-semaphore. 93462306a36Sopenharmony_ci * Actions causing state transitions of the controller must be executed while 93562306a36Sopenharmony_ci * holding the write-part of this r/w-semaphore (see ssam_controller_lock() 93662306a36Sopenharmony_ci * and ssam_controller_unlock() for that). 93762306a36Sopenharmony_ci * 93862306a36Sopenharmony_ci * See ssam_controller_stateunlock() for the corresponding unlock function. 93962306a36Sopenharmony_ci */ 94062306a36Sopenharmony_civoid ssam_controller_statelock(struct ssam_controller *c) 94162306a36Sopenharmony_ci{ 94262306a36Sopenharmony_ci down_read(&c->lock); 94362306a36Sopenharmony_ci} 94462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_controller_statelock); 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_ci/** 94762306a36Sopenharmony_ci * ssam_controller_stateunlock() - Unlock controller state transitions. 94862306a36Sopenharmony_ci * @c: The controller to unlock. 94962306a36Sopenharmony_ci * 95062306a36Sopenharmony_ci * See ssam_controller_statelock() for the corresponding lock function. 95162306a36Sopenharmony_ci */ 95262306a36Sopenharmony_civoid ssam_controller_stateunlock(struct ssam_controller *c) 95362306a36Sopenharmony_ci{ 95462306a36Sopenharmony_ci up_read(&c->lock); 95562306a36Sopenharmony_ci} 95662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_controller_stateunlock); 95762306a36Sopenharmony_ci 95862306a36Sopenharmony_ci/** 95962306a36Sopenharmony_ci * ssam_controller_lock() - Acquire the main controller lock. 96062306a36Sopenharmony_ci * @c: The controller to lock. 96162306a36Sopenharmony_ci * 96262306a36Sopenharmony_ci * This lock must be held for any state transitions, including transition to 96362306a36Sopenharmony_ci * suspend/resumed states and during shutdown. See ssam_controller_statelock() 96462306a36Sopenharmony_ci * for more details on controller locking. 96562306a36Sopenharmony_ci * 96662306a36Sopenharmony_ci * See ssam_controller_unlock() for the corresponding unlock function. 96762306a36Sopenharmony_ci */ 96862306a36Sopenharmony_civoid ssam_controller_lock(struct ssam_controller *c) 96962306a36Sopenharmony_ci{ 97062306a36Sopenharmony_ci down_write(&c->lock); 97162306a36Sopenharmony_ci} 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_ci/* 97462306a36Sopenharmony_ci * ssam_controller_unlock() - Release the main controller lock. 97562306a36Sopenharmony_ci * @c: The controller to unlock. 97662306a36Sopenharmony_ci * 97762306a36Sopenharmony_ci * See ssam_controller_lock() for the corresponding lock function. 97862306a36Sopenharmony_ci */ 97962306a36Sopenharmony_civoid ssam_controller_unlock(struct ssam_controller *c) 98062306a36Sopenharmony_ci{ 98162306a36Sopenharmony_ci up_write(&c->lock); 98262306a36Sopenharmony_ci} 98362306a36Sopenharmony_ci 98462306a36Sopenharmony_cistatic void ssam_handle_event(struct ssh_rtl *rtl, 98562306a36Sopenharmony_ci const struct ssh_command *cmd, 98662306a36Sopenharmony_ci const struct ssam_span *data) 98762306a36Sopenharmony_ci{ 98862306a36Sopenharmony_ci struct ssam_controller *ctrl = to_ssam_controller(rtl, rtl); 98962306a36Sopenharmony_ci struct ssam_event_item *item; 99062306a36Sopenharmony_ci 99162306a36Sopenharmony_ci item = ssam_event_item_alloc(data->len, GFP_KERNEL); 99262306a36Sopenharmony_ci if (!item) 99362306a36Sopenharmony_ci return; 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci item->rqid = get_unaligned_le16(&cmd->rqid); 99662306a36Sopenharmony_ci item->event.target_category = cmd->tc; 99762306a36Sopenharmony_ci item->event.target_id = cmd->sid; 99862306a36Sopenharmony_ci item->event.command_id = cmd->cid; 99962306a36Sopenharmony_ci item->event.instance_id = cmd->iid; 100062306a36Sopenharmony_ci memcpy(&item->event.data[0], data->ptr, data->len); 100162306a36Sopenharmony_ci 100262306a36Sopenharmony_ci if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item))) 100362306a36Sopenharmony_ci ssam_event_item_free(item); 100462306a36Sopenharmony_ci} 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_cistatic const struct ssh_rtl_ops ssam_rtl_ops = { 100762306a36Sopenharmony_ci .handle_event = ssam_handle_event, 100862306a36Sopenharmony_ci}; 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_cistatic bool ssam_notifier_is_empty(struct ssam_controller *ctrl); 101162306a36Sopenharmony_cistatic void ssam_notifier_unregister_all(struct ssam_controller *ctrl); 101262306a36Sopenharmony_ci 101362306a36Sopenharmony_ci#define SSAM_SSH_DSM_REVISION 0 101462306a36Sopenharmony_ci 101562306a36Sopenharmony_ci/* d5e383e1-d892-4a76-89fc-f6aaae7ed5b5 */ 101662306a36Sopenharmony_cistatic const guid_t SSAM_SSH_DSM_GUID = 101762306a36Sopenharmony_ci GUID_INIT(0xd5e383e1, 0xd892, 0x4a76, 101862306a36Sopenharmony_ci 0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5); 101962306a36Sopenharmony_ci 102062306a36Sopenharmony_cienum ssh_dsm_fn { 102162306a36Sopenharmony_ci SSH_DSM_FN_SSH_POWER_PROFILE = 0x05, 102262306a36Sopenharmony_ci SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT = 0x06, 102362306a36Sopenharmony_ci SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07, 102462306a36Sopenharmony_ci SSH_DSM_FN_D3_CLOSES_HANDLE = 0x08, 102562306a36Sopenharmony_ci SSH_DSM_FN_SSH_BUFFER_SIZE = 0x09, 102662306a36Sopenharmony_ci}; 102762306a36Sopenharmony_ci 102862306a36Sopenharmony_cistatic int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs) 102962306a36Sopenharmony_ci{ 103062306a36Sopenharmony_ci union acpi_object *obj; 103162306a36Sopenharmony_ci u64 mask = 0; 103262306a36Sopenharmony_ci int i; 103362306a36Sopenharmony_ci 103462306a36Sopenharmony_ci *funcs = 0; 103562306a36Sopenharmony_ci 103662306a36Sopenharmony_ci /* 103762306a36Sopenharmony_ci * The _DSM function is only present on newer models. It is not 103862306a36Sopenharmony_ci * present on 5th and 6th generation devices (i.e. up to and including 103962306a36Sopenharmony_ci * Surface Pro 6, Surface Laptop 2, Surface Book 2). 104062306a36Sopenharmony_ci * 104162306a36Sopenharmony_ci * If the _DSM is not present, indicate that no function is supported. 104262306a36Sopenharmony_ci * This will result in default values being set. 104362306a36Sopenharmony_ci */ 104462306a36Sopenharmony_ci if (!acpi_has_method(handle, "_DSM")) 104562306a36Sopenharmony_ci return 0; 104662306a36Sopenharmony_ci 104762306a36Sopenharmony_ci obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, 104862306a36Sopenharmony_ci SSAM_SSH_DSM_REVISION, 0, NULL, 104962306a36Sopenharmony_ci ACPI_TYPE_BUFFER); 105062306a36Sopenharmony_ci if (!obj) 105162306a36Sopenharmony_ci return -EIO; 105262306a36Sopenharmony_ci 105362306a36Sopenharmony_ci for (i = 0; i < obj->buffer.length && i < 8; i++) 105462306a36Sopenharmony_ci mask |= (((u64)obj->buffer.pointer[i]) << (i * 8)); 105562306a36Sopenharmony_ci 105662306a36Sopenharmony_ci if (mask & BIT(0)) 105762306a36Sopenharmony_ci *funcs = mask; 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci ACPI_FREE(obj); 106062306a36Sopenharmony_ci return 0; 106162306a36Sopenharmony_ci} 106262306a36Sopenharmony_ci 106362306a36Sopenharmony_cistatic int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) 106462306a36Sopenharmony_ci{ 106562306a36Sopenharmony_ci union acpi_object *obj; 106662306a36Sopenharmony_ci u64 val; 106762306a36Sopenharmony_ci 106862306a36Sopenharmony_ci if (!(funcs & BIT_ULL(func))) 106962306a36Sopenharmony_ci return 0; /* Not supported, leave *ret at its default value */ 107062306a36Sopenharmony_ci 107162306a36Sopenharmony_ci obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, 107262306a36Sopenharmony_ci SSAM_SSH_DSM_REVISION, func, NULL, 107362306a36Sopenharmony_ci ACPI_TYPE_INTEGER); 107462306a36Sopenharmony_ci if (!obj) 107562306a36Sopenharmony_ci return -EIO; 107662306a36Sopenharmony_ci 107762306a36Sopenharmony_ci val = obj->integer.value; 107862306a36Sopenharmony_ci ACPI_FREE(obj); 107962306a36Sopenharmony_ci 108062306a36Sopenharmony_ci if (val > U32_MAX) 108162306a36Sopenharmony_ci return -ERANGE; 108262306a36Sopenharmony_ci 108362306a36Sopenharmony_ci *ret = val; 108462306a36Sopenharmony_ci return 0; 108562306a36Sopenharmony_ci} 108662306a36Sopenharmony_ci 108762306a36Sopenharmony_ci/** 108862306a36Sopenharmony_ci * ssam_controller_caps_load_from_acpi() - Load controller capabilities from 108962306a36Sopenharmony_ci * ACPI _DSM. 109062306a36Sopenharmony_ci * @handle: The handle of the ACPI controller/SSH device. 109162306a36Sopenharmony_ci * @caps: Where to store the capabilities in. 109262306a36Sopenharmony_ci * 109362306a36Sopenharmony_ci * Initializes the given controller capabilities with default values, then 109462306a36Sopenharmony_ci * checks and, if the respective _DSM functions are available, loads the 109562306a36Sopenharmony_ci * actual capabilities from the _DSM. 109662306a36Sopenharmony_ci * 109762306a36Sopenharmony_ci * Return: Returns zero on success, a negative error code on failure. 109862306a36Sopenharmony_ci */ 109962306a36Sopenharmony_cistatic 110062306a36Sopenharmony_ciint ssam_controller_caps_load_from_acpi(acpi_handle handle, 110162306a36Sopenharmony_ci struct ssam_controller_caps *caps) 110262306a36Sopenharmony_ci{ 110362306a36Sopenharmony_ci u32 d3_closes_handle = false; 110462306a36Sopenharmony_ci u64 funcs; 110562306a36Sopenharmony_ci int status; 110662306a36Sopenharmony_ci 110762306a36Sopenharmony_ci /* Set defaults. */ 110862306a36Sopenharmony_ci caps->ssh_power_profile = U32_MAX; 110962306a36Sopenharmony_ci caps->screen_on_sleep_idle_timeout = U32_MAX; 111062306a36Sopenharmony_ci caps->screen_off_sleep_idle_timeout = U32_MAX; 111162306a36Sopenharmony_ci caps->d3_closes_handle = false; 111262306a36Sopenharmony_ci caps->ssh_buffer_size = U32_MAX; 111362306a36Sopenharmony_ci 111462306a36Sopenharmony_ci /* Pre-load supported DSM functions. */ 111562306a36Sopenharmony_ci status = ssam_dsm_get_functions(handle, &funcs); 111662306a36Sopenharmony_ci if (status) 111762306a36Sopenharmony_ci return status; 111862306a36Sopenharmony_ci 111962306a36Sopenharmony_ci /* Load actual values from ACPI, if present. */ 112062306a36Sopenharmony_ci status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE, 112162306a36Sopenharmony_ci &caps->ssh_power_profile); 112262306a36Sopenharmony_ci if (status) 112362306a36Sopenharmony_ci return status; 112462306a36Sopenharmony_ci 112562306a36Sopenharmony_ci status = ssam_dsm_load_u32(handle, funcs, 112662306a36Sopenharmony_ci SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT, 112762306a36Sopenharmony_ci &caps->screen_on_sleep_idle_timeout); 112862306a36Sopenharmony_ci if (status) 112962306a36Sopenharmony_ci return status; 113062306a36Sopenharmony_ci 113162306a36Sopenharmony_ci status = ssam_dsm_load_u32(handle, funcs, 113262306a36Sopenharmony_ci SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT, 113362306a36Sopenharmony_ci &caps->screen_off_sleep_idle_timeout); 113462306a36Sopenharmony_ci if (status) 113562306a36Sopenharmony_ci return status; 113662306a36Sopenharmony_ci 113762306a36Sopenharmony_ci status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE, 113862306a36Sopenharmony_ci &d3_closes_handle); 113962306a36Sopenharmony_ci if (status) 114062306a36Sopenharmony_ci return status; 114162306a36Sopenharmony_ci 114262306a36Sopenharmony_ci caps->d3_closes_handle = !!d3_closes_handle; 114362306a36Sopenharmony_ci 114462306a36Sopenharmony_ci status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE, 114562306a36Sopenharmony_ci &caps->ssh_buffer_size); 114662306a36Sopenharmony_ci if (status) 114762306a36Sopenharmony_ci return status; 114862306a36Sopenharmony_ci 114962306a36Sopenharmony_ci return 0; 115062306a36Sopenharmony_ci} 115162306a36Sopenharmony_ci 115262306a36Sopenharmony_ci/** 115362306a36Sopenharmony_ci * ssam_controller_init() - Initialize SSAM controller. 115462306a36Sopenharmony_ci * @ctrl: The controller to initialize. 115562306a36Sopenharmony_ci * @serdev: The serial device representing the underlying data transport. 115662306a36Sopenharmony_ci * 115762306a36Sopenharmony_ci * Initializes the given controller. Does neither start receiver nor 115862306a36Sopenharmony_ci * transmitter threads. After this call, the controller has to be hooked up to 115962306a36Sopenharmony_ci * the serdev core separately via &struct serdev_device_ops, relaying calls to 116062306a36Sopenharmony_ci * ssam_controller_receive_buf() and ssam_controller_write_wakeup(). Once the 116162306a36Sopenharmony_ci * controller has been hooked up, transmitter and receiver threads may be 116262306a36Sopenharmony_ci * started via ssam_controller_start(). These setup steps need to be completed 116362306a36Sopenharmony_ci * before controller can be used for requests. 116462306a36Sopenharmony_ci */ 116562306a36Sopenharmony_ciint ssam_controller_init(struct ssam_controller *ctrl, 116662306a36Sopenharmony_ci struct serdev_device *serdev) 116762306a36Sopenharmony_ci{ 116862306a36Sopenharmony_ci acpi_handle handle = ACPI_HANDLE(&serdev->dev); 116962306a36Sopenharmony_ci int status; 117062306a36Sopenharmony_ci 117162306a36Sopenharmony_ci init_rwsem(&ctrl->lock); 117262306a36Sopenharmony_ci kref_init(&ctrl->kref); 117362306a36Sopenharmony_ci 117462306a36Sopenharmony_ci status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps); 117562306a36Sopenharmony_ci if (status) 117662306a36Sopenharmony_ci return status; 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci dev_dbg(&serdev->dev, 117962306a36Sopenharmony_ci "device capabilities:\n" 118062306a36Sopenharmony_ci " ssh_power_profile: %u\n" 118162306a36Sopenharmony_ci " ssh_buffer_size: %u\n" 118262306a36Sopenharmony_ci " screen_on_sleep_idle_timeout: %u\n" 118362306a36Sopenharmony_ci " screen_off_sleep_idle_timeout: %u\n" 118462306a36Sopenharmony_ci " d3_closes_handle: %u\n", 118562306a36Sopenharmony_ci ctrl->caps.ssh_power_profile, 118662306a36Sopenharmony_ci ctrl->caps.ssh_buffer_size, 118762306a36Sopenharmony_ci ctrl->caps.screen_on_sleep_idle_timeout, 118862306a36Sopenharmony_ci ctrl->caps.screen_off_sleep_idle_timeout, 118962306a36Sopenharmony_ci ctrl->caps.d3_closes_handle); 119062306a36Sopenharmony_ci 119162306a36Sopenharmony_ci ssh_seq_reset(&ctrl->counter.seq); 119262306a36Sopenharmony_ci ssh_rqid_reset(&ctrl->counter.rqid); 119362306a36Sopenharmony_ci 119462306a36Sopenharmony_ci /* Initialize event/request completion system. */ 119562306a36Sopenharmony_ci status = ssam_cplt_init(&ctrl->cplt, &serdev->dev); 119662306a36Sopenharmony_ci if (status) 119762306a36Sopenharmony_ci return status; 119862306a36Sopenharmony_ci 119962306a36Sopenharmony_ci /* Initialize request and packet transport layers. */ 120062306a36Sopenharmony_ci status = ssh_rtl_init(&ctrl->rtl, serdev, &ssam_rtl_ops); 120162306a36Sopenharmony_ci if (status) { 120262306a36Sopenharmony_ci ssam_cplt_destroy(&ctrl->cplt); 120362306a36Sopenharmony_ci return status; 120462306a36Sopenharmony_ci } 120562306a36Sopenharmony_ci 120662306a36Sopenharmony_ci /* 120762306a36Sopenharmony_ci * Set state via write_once even though we expect to be in an 120862306a36Sopenharmony_ci * exclusive context, due to smoke-testing in 120962306a36Sopenharmony_ci * ssam_request_sync_submit(). 121062306a36Sopenharmony_ci */ 121162306a36Sopenharmony_ci WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_INITIALIZED); 121262306a36Sopenharmony_ci return 0; 121362306a36Sopenharmony_ci} 121462306a36Sopenharmony_ci 121562306a36Sopenharmony_ci/** 121662306a36Sopenharmony_ci * ssam_controller_start() - Start the receiver and transmitter threads of the 121762306a36Sopenharmony_ci * controller. 121862306a36Sopenharmony_ci * @ctrl: The controller. 121962306a36Sopenharmony_ci * 122062306a36Sopenharmony_ci * Note: When this function is called, the controller should be properly 122162306a36Sopenharmony_ci * hooked up to the serdev core via &struct serdev_device_ops. Please refer 122262306a36Sopenharmony_ci * to ssam_controller_init() for more details on controller initialization. 122362306a36Sopenharmony_ci * 122462306a36Sopenharmony_ci * This function must be called with the main controller lock held (i.e. by 122562306a36Sopenharmony_ci * calling ssam_controller_lock()). 122662306a36Sopenharmony_ci */ 122762306a36Sopenharmony_ciint ssam_controller_start(struct ssam_controller *ctrl) 122862306a36Sopenharmony_ci{ 122962306a36Sopenharmony_ci int status; 123062306a36Sopenharmony_ci 123162306a36Sopenharmony_ci lockdep_assert_held_write(&ctrl->lock); 123262306a36Sopenharmony_ci 123362306a36Sopenharmony_ci if (ctrl->state != SSAM_CONTROLLER_INITIALIZED) 123462306a36Sopenharmony_ci return -EINVAL; 123562306a36Sopenharmony_ci 123662306a36Sopenharmony_ci status = ssh_rtl_start(&ctrl->rtl); 123762306a36Sopenharmony_ci if (status) 123862306a36Sopenharmony_ci return status; 123962306a36Sopenharmony_ci 124062306a36Sopenharmony_ci /* 124162306a36Sopenharmony_ci * Set state via write_once even though we expect to be locked/in an 124262306a36Sopenharmony_ci * exclusive context, due to smoke-testing in 124362306a36Sopenharmony_ci * ssam_request_sync_submit(). 124462306a36Sopenharmony_ci */ 124562306a36Sopenharmony_ci WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); 124662306a36Sopenharmony_ci return 0; 124762306a36Sopenharmony_ci} 124862306a36Sopenharmony_ci 124962306a36Sopenharmony_ci/* 125062306a36Sopenharmony_ci * SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT - Timeout for flushing requests during 125162306a36Sopenharmony_ci * shutdown. 125262306a36Sopenharmony_ci * 125362306a36Sopenharmony_ci * Chosen to be larger than one full request timeout, including packets timing 125462306a36Sopenharmony_ci * out. This value should give ample time to complete any outstanding requests 125562306a36Sopenharmony_ci * during normal operation and account for the odd package timeout. 125662306a36Sopenharmony_ci */ 125762306a36Sopenharmony_ci#define SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT msecs_to_jiffies(5000) 125862306a36Sopenharmony_ci 125962306a36Sopenharmony_ci/** 126062306a36Sopenharmony_ci * ssam_controller_shutdown() - Shut down the controller. 126162306a36Sopenharmony_ci * @ctrl: The controller. 126262306a36Sopenharmony_ci * 126362306a36Sopenharmony_ci * Shuts down the controller by flushing all pending requests and stopping the 126462306a36Sopenharmony_ci * transmitter and receiver threads. All requests submitted after this call 126562306a36Sopenharmony_ci * will fail with %-ESHUTDOWN. While it is discouraged to do so, this function 126662306a36Sopenharmony_ci * is safe to use in parallel with ongoing request submission. 126762306a36Sopenharmony_ci * 126862306a36Sopenharmony_ci * In the course of this shutdown procedure, all currently registered 126962306a36Sopenharmony_ci * notifiers will be unregistered. It is, however, strongly recommended to not 127062306a36Sopenharmony_ci * rely on this behavior, and instead the party registering the notifier 127162306a36Sopenharmony_ci * should unregister it before the controller gets shut down, e.g. via the 127262306a36Sopenharmony_ci * SSAM bus which guarantees client devices to be removed before a shutdown. 127362306a36Sopenharmony_ci * 127462306a36Sopenharmony_ci * Note that events may still be pending after this call, but, due to the 127562306a36Sopenharmony_ci * notifiers being unregistered, these events will be dropped when the 127662306a36Sopenharmony_ci * controller is subsequently destroyed via ssam_controller_destroy(). 127762306a36Sopenharmony_ci * 127862306a36Sopenharmony_ci * This function must be called with the main controller lock held (i.e. by 127962306a36Sopenharmony_ci * calling ssam_controller_lock()). 128062306a36Sopenharmony_ci */ 128162306a36Sopenharmony_civoid ssam_controller_shutdown(struct ssam_controller *ctrl) 128262306a36Sopenharmony_ci{ 128362306a36Sopenharmony_ci enum ssam_controller_state s = ctrl->state; 128462306a36Sopenharmony_ci int status; 128562306a36Sopenharmony_ci 128662306a36Sopenharmony_ci lockdep_assert_held_write(&ctrl->lock); 128762306a36Sopenharmony_ci 128862306a36Sopenharmony_ci if (s == SSAM_CONTROLLER_UNINITIALIZED || s == SSAM_CONTROLLER_STOPPED) 128962306a36Sopenharmony_ci return; 129062306a36Sopenharmony_ci 129162306a36Sopenharmony_ci /* 129262306a36Sopenharmony_ci * Try to flush pending events and requests while everything still 129362306a36Sopenharmony_ci * works. Note: There may still be packets and/or requests in the 129462306a36Sopenharmony_ci * system after this call (e.g. via control packets submitted by the 129562306a36Sopenharmony_ci * packet transport layer or flush timeout / failure, ...). Those will 129662306a36Sopenharmony_ci * be handled with the ssh_rtl_shutdown() call below. 129762306a36Sopenharmony_ci */ 129862306a36Sopenharmony_ci status = ssh_rtl_flush(&ctrl->rtl, SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT); 129962306a36Sopenharmony_ci if (status) { 130062306a36Sopenharmony_ci ssam_err(ctrl, "failed to flush request transport layer: %d\n", 130162306a36Sopenharmony_ci status); 130262306a36Sopenharmony_ci } 130362306a36Sopenharmony_ci 130462306a36Sopenharmony_ci /* Try to flush all currently completing requests and events. */ 130562306a36Sopenharmony_ci ssam_cplt_flush(&ctrl->cplt); 130662306a36Sopenharmony_ci 130762306a36Sopenharmony_ci /* 130862306a36Sopenharmony_ci * We expect all notifiers to have been removed by the respective client 130962306a36Sopenharmony_ci * driver that set them up at this point. If this warning occurs, some 131062306a36Sopenharmony_ci * client driver has not done that... 131162306a36Sopenharmony_ci */ 131262306a36Sopenharmony_ci WARN_ON(!ssam_notifier_is_empty(ctrl)); 131362306a36Sopenharmony_ci 131462306a36Sopenharmony_ci /* 131562306a36Sopenharmony_ci * Nevertheless, we should still take care of drivers that don't behave 131662306a36Sopenharmony_ci * well. Thus disable all enabled events, unregister all notifiers. 131762306a36Sopenharmony_ci */ 131862306a36Sopenharmony_ci ssam_notifier_unregister_all(ctrl); 131962306a36Sopenharmony_ci 132062306a36Sopenharmony_ci /* 132162306a36Sopenharmony_ci * Cancel remaining requests. Ensure no new ones can be queued and stop 132262306a36Sopenharmony_ci * threads. 132362306a36Sopenharmony_ci */ 132462306a36Sopenharmony_ci ssh_rtl_shutdown(&ctrl->rtl); 132562306a36Sopenharmony_ci 132662306a36Sopenharmony_ci /* 132762306a36Sopenharmony_ci * Set state via write_once even though we expect to be locked/in an 132862306a36Sopenharmony_ci * exclusive context, due to smoke-testing in 132962306a36Sopenharmony_ci * ssam_request_sync_submit(). 133062306a36Sopenharmony_ci */ 133162306a36Sopenharmony_ci WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STOPPED); 133262306a36Sopenharmony_ci ctrl->rtl.ptl.serdev = NULL; 133362306a36Sopenharmony_ci} 133462306a36Sopenharmony_ci 133562306a36Sopenharmony_ci/** 133662306a36Sopenharmony_ci * ssam_controller_destroy() - Destroy the controller and free its resources. 133762306a36Sopenharmony_ci * @ctrl: The controller. 133862306a36Sopenharmony_ci * 133962306a36Sopenharmony_ci * Ensures that all resources associated with the controller get freed. This 134062306a36Sopenharmony_ci * function should only be called after the controller has been stopped via 134162306a36Sopenharmony_ci * ssam_controller_shutdown(). In general, this function should not be called 134262306a36Sopenharmony_ci * directly. The only valid place to call this function directly is during 134362306a36Sopenharmony_ci * initialization, before the controller has been fully initialized and passed 134462306a36Sopenharmony_ci * to other processes. This function is called automatically when the 134562306a36Sopenharmony_ci * reference count of the controller reaches zero. 134662306a36Sopenharmony_ci * 134762306a36Sopenharmony_ci * This function must be called with the main controller lock held (i.e. by 134862306a36Sopenharmony_ci * calling ssam_controller_lock()). 134962306a36Sopenharmony_ci */ 135062306a36Sopenharmony_civoid ssam_controller_destroy(struct ssam_controller *ctrl) 135162306a36Sopenharmony_ci{ 135262306a36Sopenharmony_ci lockdep_assert_held_write(&ctrl->lock); 135362306a36Sopenharmony_ci 135462306a36Sopenharmony_ci if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) 135562306a36Sopenharmony_ci return; 135662306a36Sopenharmony_ci 135762306a36Sopenharmony_ci WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); 135862306a36Sopenharmony_ci 135962306a36Sopenharmony_ci /* 136062306a36Sopenharmony_ci * Note: New events could still have been received after the previous 136162306a36Sopenharmony_ci * flush in ssam_controller_shutdown, before the request transport layer 136262306a36Sopenharmony_ci * has been shut down. At this point, after the shutdown, we can be sure 136362306a36Sopenharmony_ci * that no new events will be queued. The call to ssam_cplt_destroy will 136462306a36Sopenharmony_ci * ensure that those remaining are being completed and freed. 136562306a36Sopenharmony_ci */ 136662306a36Sopenharmony_ci 136762306a36Sopenharmony_ci /* Actually free resources. */ 136862306a36Sopenharmony_ci ssam_cplt_destroy(&ctrl->cplt); 136962306a36Sopenharmony_ci ssh_rtl_destroy(&ctrl->rtl); 137062306a36Sopenharmony_ci 137162306a36Sopenharmony_ci /* 137262306a36Sopenharmony_ci * Set state via write_once even though we expect to be locked/in an 137362306a36Sopenharmony_ci * exclusive context, due to smoke-testing in 137462306a36Sopenharmony_ci * ssam_request_sync_submit(). 137562306a36Sopenharmony_ci */ 137662306a36Sopenharmony_ci WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_UNINITIALIZED); 137762306a36Sopenharmony_ci} 137862306a36Sopenharmony_ci 137962306a36Sopenharmony_ci/** 138062306a36Sopenharmony_ci * ssam_controller_suspend() - Suspend the controller. 138162306a36Sopenharmony_ci * @ctrl: The controller to suspend. 138262306a36Sopenharmony_ci * 138362306a36Sopenharmony_ci * Marks the controller as suspended. Note that display-off and D0-exit 138462306a36Sopenharmony_ci * notifications have to be sent manually before transitioning the controller 138562306a36Sopenharmony_ci * into the suspended state via this function. 138662306a36Sopenharmony_ci * 138762306a36Sopenharmony_ci * See ssam_controller_resume() for the corresponding resume function. 138862306a36Sopenharmony_ci * 138962306a36Sopenharmony_ci * Return: Returns %-EINVAL if the controller is currently not in the 139062306a36Sopenharmony_ci * "started" state. 139162306a36Sopenharmony_ci */ 139262306a36Sopenharmony_ciint ssam_controller_suspend(struct ssam_controller *ctrl) 139362306a36Sopenharmony_ci{ 139462306a36Sopenharmony_ci ssam_controller_lock(ctrl); 139562306a36Sopenharmony_ci 139662306a36Sopenharmony_ci if (ctrl->state != SSAM_CONTROLLER_STARTED) { 139762306a36Sopenharmony_ci ssam_controller_unlock(ctrl); 139862306a36Sopenharmony_ci return -EINVAL; 139962306a36Sopenharmony_ci } 140062306a36Sopenharmony_ci 140162306a36Sopenharmony_ci ssam_dbg(ctrl, "pm: suspending controller\n"); 140262306a36Sopenharmony_ci 140362306a36Sopenharmony_ci /* 140462306a36Sopenharmony_ci * Set state via write_once even though we're locked, due to 140562306a36Sopenharmony_ci * smoke-testing in ssam_request_sync_submit(). 140662306a36Sopenharmony_ci */ 140762306a36Sopenharmony_ci WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_SUSPENDED); 140862306a36Sopenharmony_ci 140962306a36Sopenharmony_ci ssam_controller_unlock(ctrl); 141062306a36Sopenharmony_ci return 0; 141162306a36Sopenharmony_ci} 141262306a36Sopenharmony_ci 141362306a36Sopenharmony_ci/** 141462306a36Sopenharmony_ci * ssam_controller_resume() - Resume the controller from suspend. 141562306a36Sopenharmony_ci * @ctrl: The controller to resume. 141662306a36Sopenharmony_ci * 141762306a36Sopenharmony_ci * Resume the controller from the suspended state it was put into via 141862306a36Sopenharmony_ci * ssam_controller_suspend(). This function does not issue display-on and 141962306a36Sopenharmony_ci * D0-entry notifications. If required, those have to be sent manually after 142062306a36Sopenharmony_ci * this call. 142162306a36Sopenharmony_ci * 142262306a36Sopenharmony_ci * Return: Returns %-EINVAL if the controller is currently not suspended. 142362306a36Sopenharmony_ci */ 142462306a36Sopenharmony_ciint ssam_controller_resume(struct ssam_controller *ctrl) 142562306a36Sopenharmony_ci{ 142662306a36Sopenharmony_ci ssam_controller_lock(ctrl); 142762306a36Sopenharmony_ci 142862306a36Sopenharmony_ci if (ctrl->state != SSAM_CONTROLLER_SUSPENDED) { 142962306a36Sopenharmony_ci ssam_controller_unlock(ctrl); 143062306a36Sopenharmony_ci return -EINVAL; 143162306a36Sopenharmony_ci } 143262306a36Sopenharmony_ci 143362306a36Sopenharmony_ci ssam_dbg(ctrl, "pm: resuming controller\n"); 143462306a36Sopenharmony_ci 143562306a36Sopenharmony_ci /* 143662306a36Sopenharmony_ci * Set state via write_once even though we're locked, due to 143762306a36Sopenharmony_ci * smoke-testing in ssam_request_sync_submit(). 143862306a36Sopenharmony_ci */ 143962306a36Sopenharmony_ci WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); 144062306a36Sopenharmony_ci 144162306a36Sopenharmony_ci ssam_controller_unlock(ctrl); 144262306a36Sopenharmony_ci return 0; 144362306a36Sopenharmony_ci} 144462306a36Sopenharmony_ci 144562306a36Sopenharmony_ci 144662306a36Sopenharmony_ci/* -- Top-level request interface ------------------------------------------- */ 144762306a36Sopenharmony_ci 144862306a36Sopenharmony_ci/** 144962306a36Sopenharmony_ci * ssam_request_write_data() - Construct and write SAM request message to 145062306a36Sopenharmony_ci * buffer. 145162306a36Sopenharmony_ci * @buf: The buffer to write the data to. 145262306a36Sopenharmony_ci * @ctrl: The controller via which the request will be sent. 145362306a36Sopenharmony_ci * @spec: The request data and specification. 145462306a36Sopenharmony_ci * 145562306a36Sopenharmony_ci * Constructs a SAM/SSH request message and writes it to the provided buffer. 145662306a36Sopenharmony_ci * The request and transport counters, specifically RQID and SEQ, will be set 145762306a36Sopenharmony_ci * in this call. These counters are obtained from the controller. It is thus 145862306a36Sopenharmony_ci * only valid to send the resulting message via the controller specified here. 145962306a36Sopenharmony_ci * 146062306a36Sopenharmony_ci * For calculation of the required buffer size, refer to the 146162306a36Sopenharmony_ci * SSH_COMMAND_MESSAGE_LENGTH() macro. 146262306a36Sopenharmony_ci * 146362306a36Sopenharmony_ci * Return: Returns the number of bytes used in the buffer on success. Returns 146462306a36Sopenharmony_ci * %-EINVAL if the payload length provided in the request specification is too 146562306a36Sopenharmony_ci * large (larger than %SSH_COMMAND_MAX_PAYLOAD_SIZE) or if the provided buffer 146662306a36Sopenharmony_ci * is too small. 146762306a36Sopenharmony_ci */ 146862306a36Sopenharmony_cissize_t ssam_request_write_data(struct ssam_span *buf, 146962306a36Sopenharmony_ci struct ssam_controller *ctrl, 147062306a36Sopenharmony_ci const struct ssam_request *spec) 147162306a36Sopenharmony_ci{ 147262306a36Sopenharmony_ci struct msgbuf msgb; 147362306a36Sopenharmony_ci u16 rqid; 147462306a36Sopenharmony_ci u8 seq; 147562306a36Sopenharmony_ci 147662306a36Sopenharmony_ci if (spec->length > SSH_COMMAND_MAX_PAYLOAD_SIZE) 147762306a36Sopenharmony_ci return -EINVAL; 147862306a36Sopenharmony_ci 147962306a36Sopenharmony_ci if (SSH_COMMAND_MESSAGE_LENGTH(spec->length) > buf->len) 148062306a36Sopenharmony_ci return -EINVAL; 148162306a36Sopenharmony_ci 148262306a36Sopenharmony_ci msgb_init(&msgb, buf->ptr, buf->len); 148362306a36Sopenharmony_ci seq = ssh_seq_next(&ctrl->counter.seq); 148462306a36Sopenharmony_ci rqid = ssh_rqid_next(&ctrl->counter.rqid); 148562306a36Sopenharmony_ci msgb_push_cmd(&msgb, seq, rqid, spec); 148662306a36Sopenharmony_ci 148762306a36Sopenharmony_ci return msgb_bytes_used(&msgb); 148862306a36Sopenharmony_ci} 148962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_request_write_data); 149062306a36Sopenharmony_ci 149162306a36Sopenharmony_cistatic void ssam_request_sync_complete(struct ssh_request *rqst, 149262306a36Sopenharmony_ci const struct ssh_command *cmd, 149362306a36Sopenharmony_ci const struct ssam_span *data, int status) 149462306a36Sopenharmony_ci{ 149562306a36Sopenharmony_ci struct ssh_rtl *rtl = ssh_request_rtl(rqst); 149662306a36Sopenharmony_ci struct ssam_request_sync *r; 149762306a36Sopenharmony_ci 149862306a36Sopenharmony_ci r = container_of(rqst, struct ssam_request_sync, base); 149962306a36Sopenharmony_ci r->status = status; 150062306a36Sopenharmony_ci 150162306a36Sopenharmony_ci if (r->resp) 150262306a36Sopenharmony_ci r->resp->length = 0; 150362306a36Sopenharmony_ci 150462306a36Sopenharmony_ci if (status) { 150562306a36Sopenharmony_ci rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status); 150662306a36Sopenharmony_ci return; 150762306a36Sopenharmony_ci } 150862306a36Sopenharmony_ci 150962306a36Sopenharmony_ci if (!data) /* Handle requests without a response. */ 151062306a36Sopenharmony_ci return; 151162306a36Sopenharmony_ci 151262306a36Sopenharmony_ci if (!r->resp || !r->resp->pointer) { 151362306a36Sopenharmony_ci if (data->len) 151462306a36Sopenharmony_ci rtl_warn(rtl, "rsp: no response buffer provided, dropping data\n"); 151562306a36Sopenharmony_ci return; 151662306a36Sopenharmony_ci } 151762306a36Sopenharmony_ci 151862306a36Sopenharmony_ci if (data->len > r->resp->capacity) { 151962306a36Sopenharmony_ci rtl_err(rtl, 152062306a36Sopenharmony_ci "rsp: response buffer too small, capacity: %zu bytes, got: %zu bytes\n", 152162306a36Sopenharmony_ci r->resp->capacity, data->len); 152262306a36Sopenharmony_ci r->status = -ENOSPC; 152362306a36Sopenharmony_ci return; 152462306a36Sopenharmony_ci } 152562306a36Sopenharmony_ci 152662306a36Sopenharmony_ci r->resp->length = data->len; 152762306a36Sopenharmony_ci memcpy(r->resp->pointer, data->ptr, data->len); 152862306a36Sopenharmony_ci} 152962306a36Sopenharmony_ci 153062306a36Sopenharmony_cistatic void ssam_request_sync_release(struct ssh_request *rqst) 153162306a36Sopenharmony_ci{ 153262306a36Sopenharmony_ci complete_all(&container_of(rqst, struct ssam_request_sync, base)->comp); 153362306a36Sopenharmony_ci} 153462306a36Sopenharmony_ci 153562306a36Sopenharmony_cistatic const struct ssh_request_ops ssam_request_sync_ops = { 153662306a36Sopenharmony_ci .release = ssam_request_sync_release, 153762306a36Sopenharmony_ci .complete = ssam_request_sync_complete, 153862306a36Sopenharmony_ci}; 153962306a36Sopenharmony_ci 154062306a36Sopenharmony_ci/** 154162306a36Sopenharmony_ci * ssam_request_sync_alloc() - Allocate a synchronous request. 154262306a36Sopenharmony_ci * @payload_len: The length of the request payload. 154362306a36Sopenharmony_ci * @flags: Flags used for allocation. 154462306a36Sopenharmony_ci * @rqst: Where to store the pointer to the allocated request. 154562306a36Sopenharmony_ci * @buffer: Where to store the buffer descriptor for the message buffer of 154662306a36Sopenharmony_ci * the request. 154762306a36Sopenharmony_ci * 154862306a36Sopenharmony_ci * Allocates a synchronous request with corresponding message buffer. The 154962306a36Sopenharmony_ci * request still needs to be initialized ssam_request_sync_init() before 155062306a36Sopenharmony_ci * it can be submitted, and the message buffer data must still be set to the 155162306a36Sopenharmony_ci * returned buffer via ssam_request_sync_set_data() after it has been filled, 155262306a36Sopenharmony_ci * if need be with adjusted message length. 155362306a36Sopenharmony_ci * 155462306a36Sopenharmony_ci * After use, the request and its corresponding message buffer should be freed 155562306a36Sopenharmony_ci * via ssam_request_sync_free(). The buffer must not be freed separately. 155662306a36Sopenharmony_ci * 155762306a36Sopenharmony_ci * Return: Returns zero on success, %-ENOMEM if the request could not be 155862306a36Sopenharmony_ci * allocated. 155962306a36Sopenharmony_ci */ 156062306a36Sopenharmony_ciint ssam_request_sync_alloc(size_t payload_len, gfp_t flags, 156162306a36Sopenharmony_ci struct ssam_request_sync **rqst, 156262306a36Sopenharmony_ci struct ssam_span *buffer) 156362306a36Sopenharmony_ci{ 156462306a36Sopenharmony_ci size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len); 156562306a36Sopenharmony_ci 156662306a36Sopenharmony_ci *rqst = kzalloc(sizeof(**rqst) + msglen, flags); 156762306a36Sopenharmony_ci if (!*rqst) 156862306a36Sopenharmony_ci return -ENOMEM; 156962306a36Sopenharmony_ci 157062306a36Sopenharmony_ci buffer->ptr = (u8 *)(*rqst + 1); 157162306a36Sopenharmony_ci buffer->len = msglen; 157262306a36Sopenharmony_ci 157362306a36Sopenharmony_ci return 0; 157462306a36Sopenharmony_ci} 157562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_request_sync_alloc); 157662306a36Sopenharmony_ci 157762306a36Sopenharmony_ci/** 157862306a36Sopenharmony_ci * ssam_request_sync_free() - Free a synchronous request. 157962306a36Sopenharmony_ci * @rqst: The request to be freed. 158062306a36Sopenharmony_ci * 158162306a36Sopenharmony_ci * Free a synchronous request and its corresponding buffer allocated with 158262306a36Sopenharmony_ci * ssam_request_sync_alloc(). Do not use for requests allocated on the stack 158362306a36Sopenharmony_ci * or via any other function. 158462306a36Sopenharmony_ci * 158562306a36Sopenharmony_ci * Warning: The caller must ensure that the request is not in use any more. 158662306a36Sopenharmony_ci * I.e. the caller must ensure that it has the only reference to the request 158762306a36Sopenharmony_ci * and the request is not currently pending. This means that the caller has 158862306a36Sopenharmony_ci * either never submitted the request, request submission has failed, or the 158962306a36Sopenharmony_ci * caller has waited until the submitted request has been completed via 159062306a36Sopenharmony_ci * ssam_request_sync_wait(). 159162306a36Sopenharmony_ci */ 159262306a36Sopenharmony_civoid ssam_request_sync_free(struct ssam_request_sync *rqst) 159362306a36Sopenharmony_ci{ 159462306a36Sopenharmony_ci kfree(rqst); 159562306a36Sopenharmony_ci} 159662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_request_sync_free); 159762306a36Sopenharmony_ci 159862306a36Sopenharmony_ci/** 159962306a36Sopenharmony_ci * ssam_request_sync_init() - Initialize a synchronous request struct. 160062306a36Sopenharmony_ci * @rqst: The request to initialize. 160162306a36Sopenharmony_ci * @flags: The request flags. 160262306a36Sopenharmony_ci * 160362306a36Sopenharmony_ci * Initializes the given request struct. Does not initialize the request 160462306a36Sopenharmony_ci * message data. This has to be done explicitly after this call via 160562306a36Sopenharmony_ci * ssam_request_sync_set_data() and the actual message data has to be written 160662306a36Sopenharmony_ci * via ssam_request_write_data(). 160762306a36Sopenharmony_ci * 160862306a36Sopenharmony_ci * Return: Returns zero on success or %-EINVAL if the given flags are invalid. 160962306a36Sopenharmony_ci */ 161062306a36Sopenharmony_ciint ssam_request_sync_init(struct ssam_request_sync *rqst, 161162306a36Sopenharmony_ci enum ssam_request_flags flags) 161262306a36Sopenharmony_ci{ 161362306a36Sopenharmony_ci int status; 161462306a36Sopenharmony_ci 161562306a36Sopenharmony_ci status = ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops); 161662306a36Sopenharmony_ci if (status) 161762306a36Sopenharmony_ci return status; 161862306a36Sopenharmony_ci 161962306a36Sopenharmony_ci init_completion(&rqst->comp); 162062306a36Sopenharmony_ci rqst->resp = NULL; 162162306a36Sopenharmony_ci rqst->status = 0; 162262306a36Sopenharmony_ci 162362306a36Sopenharmony_ci return 0; 162462306a36Sopenharmony_ci} 162562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_request_sync_init); 162662306a36Sopenharmony_ci 162762306a36Sopenharmony_ci/** 162862306a36Sopenharmony_ci * ssam_request_sync_submit() - Submit a synchronous request. 162962306a36Sopenharmony_ci * @ctrl: The controller with which to submit the request. 163062306a36Sopenharmony_ci * @rqst: The request to submit. 163162306a36Sopenharmony_ci * 163262306a36Sopenharmony_ci * Submit a synchronous request. The request has to be initialized and 163362306a36Sopenharmony_ci * properly set up, including response buffer (may be %NULL if no response is 163462306a36Sopenharmony_ci * expected) and command message data. This function does not wait for the 163562306a36Sopenharmony_ci * request to be completed. 163662306a36Sopenharmony_ci * 163762306a36Sopenharmony_ci * If this function succeeds, ssam_request_sync_wait() must be used to ensure 163862306a36Sopenharmony_ci * that the request has been completed before the response data can be 163962306a36Sopenharmony_ci * accessed and/or the request can be freed. On failure, the request may 164062306a36Sopenharmony_ci * immediately be freed. 164162306a36Sopenharmony_ci * 164262306a36Sopenharmony_ci * This function may only be used if the controller is active, i.e. has been 164362306a36Sopenharmony_ci * initialized and not suspended. 164462306a36Sopenharmony_ci */ 164562306a36Sopenharmony_ciint ssam_request_sync_submit(struct ssam_controller *ctrl, 164662306a36Sopenharmony_ci struct ssam_request_sync *rqst) 164762306a36Sopenharmony_ci{ 164862306a36Sopenharmony_ci int status; 164962306a36Sopenharmony_ci 165062306a36Sopenharmony_ci /* 165162306a36Sopenharmony_ci * This is only a superficial check. In general, the caller needs to 165262306a36Sopenharmony_ci * ensure that the controller is initialized and is not (and does not 165362306a36Sopenharmony_ci * get) suspended during use, i.e. until the request has been completed 165462306a36Sopenharmony_ci * (if _absolutely_ necessary, by use of ssam_controller_statelock/ 165562306a36Sopenharmony_ci * ssam_controller_stateunlock, but something like ssam_client_link 165662306a36Sopenharmony_ci * should be preferred as this needs to last until the request has been 165762306a36Sopenharmony_ci * completed). 165862306a36Sopenharmony_ci * 165962306a36Sopenharmony_ci * Note that it is actually safe to use this function while the 166062306a36Sopenharmony_ci * controller is in the process of being shut down (as ssh_rtl_submit 166162306a36Sopenharmony_ci * is safe with regards to this), but it is generally discouraged to do 166262306a36Sopenharmony_ci * so. 166362306a36Sopenharmony_ci */ 166462306a36Sopenharmony_ci if (WARN_ON(READ_ONCE(ctrl->state) != SSAM_CONTROLLER_STARTED)) { 166562306a36Sopenharmony_ci ssh_request_put(&rqst->base); 166662306a36Sopenharmony_ci return -ENODEV; 166762306a36Sopenharmony_ci } 166862306a36Sopenharmony_ci 166962306a36Sopenharmony_ci status = ssh_rtl_submit(&ctrl->rtl, &rqst->base); 167062306a36Sopenharmony_ci ssh_request_put(&rqst->base); 167162306a36Sopenharmony_ci 167262306a36Sopenharmony_ci return status; 167362306a36Sopenharmony_ci} 167462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_request_sync_submit); 167562306a36Sopenharmony_ci 167662306a36Sopenharmony_ci/** 167762306a36Sopenharmony_ci * ssam_request_do_sync() - Execute a synchronous request. 167862306a36Sopenharmony_ci * @ctrl: The controller via which the request will be submitted. 167962306a36Sopenharmony_ci * @spec: The request specification and payload. 168062306a36Sopenharmony_ci * @rsp: The response buffer. 168162306a36Sopenharmony_ci * 168262306a36Sopenharmony_ci * Allocates a synchronous request with its message data buffer on the heap 168362306a36Sopenharmony_ci * via ssam_request_sync_alloc(), fully initializes it via the provided 168462306a36Sopenharmony_ci * request specification, submits it, and finally waits for its completion 168562306a36Sopenharmony_ci * before freeing it and returning its status. 168662306a36Sopenharmony_ci * 168762306a36Sopenharmony_ci * Return: Returns the status of the request or any failure during setup. 168862306a36Sopenharmony_ci */ 168962306a36Sopenharmony_ciint ssam_request_do_sync(struct ssam_controller *ctrl, 169062306a36Sopenharmony_ci const struct ssam_request *spec, 169162306a36Sopenharmony_ci struct ssam_response *rsp) 169262306a36Sopenharmony_ci{ 169362306a36Sopenharmony_ci struct ssam_request_sync *rqst; 169462306a36Sopenharmony_ci struct ssam_span buf; 169562306a36Sopenharmony_ci ssize_t len; 169662306a36Sopenharmony_ci int status; 169762306a36Sopenharmony_ci 169862306a36Sopenharmony_ci status = ssam_request_sync_alloc(spec->length, GFP_KERNEL, &rqst, &buf); 169962306a36Sopenharmony_ci if (status) 170062306a36Sopenharmony_ci return status; 170162306a36Sopenharmony_ci 170262306a36Sopenharmony_ci status = ssam_request_sync_init(rqst, spec->flags); 170362306a36Sopenharmony_ci if (status) { 170462306a36Sopenharmony_ci ssam_request_sync_free(rqst); 170562306a36Sopenharmony_ci return status; 170662306a36Sopenharmony_ci } 170762306a36Sopenharmony_ci 170862306a36Sopenharmony_ci ssam_request_sync_set_resp(rqst, rsp); 170962306a36Sopenharmony_ci 171062306a36Sopenharmony_ci len = ssam_request_write_data(&buf, ctrl, spec); 171162306a36Sopenharmony_ci if (len < 0) { 171262306a36Sopenharmony_ci ssam_request_sync_free(rqst); 171362306a36Sopenharmony_ci return len; 171462306a36Sopenharmony_ci } 171562306a36Sopenharmony_ci 171662306a36Sopenharmony_ci ssam_request_sync_set_data(rqst, buf.ptr, len); 171762306a36Sopenharmony_ci 171862306a36Sopenharmony_ci status = ssam_request_sync_submit(ctrl, rqst); 171962306a36Sopenharmony_ci if (!status) 172062306a36Sopenharmony_ci status = ssam_request_sync_wait(rqst); 172162306a36Sopenharmony_ci 172262306a36Sopenharmony_ci ssam_request_sync_free(rqst); 172362306a36Sopenharmony_ci return status; 172462306a36Sopenharmony_ci} 172562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_request_do_sync); 172662306a36Sopenharmony_ci 172762306a36Sopenharmony_ci/** 172862306a36Sopenharmony_ci * ssam_request_do_sync_with_buffer() - Execute a synchronous request with the 172962306a36Sopenharmony_ci * provided buffer as back-end for the message buffer. 173062306a36Sopenharmony_ci * @ctrl: The controller via which the request will be submitted. 173162306a36Sopenharmony_ci * @spec: The request specification and payload. 173262306a36Sopenharmony_ci * @rsp: The response buffer. 173362306a36Sopenharmony_ci * @buf: The buffer for the request message data. 173462306a36Sopenharmony_ci * 173562306a36Sopenharmony_ci * Allocates a synchronous request struct on the stack, fully initializes it 173662306a36Sopenharmony_ci * using the provided buffer as message data buffer, submits it, and then 173762306a36Sopenharmony_ci * waits for its completion before returning its status. The 173862306a36Sopenharmony_ci * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required 173962306a36Sopenharmony_ci * message buffer size. 174062306a36Sopenharmony_ci * 174162306a36Sopenharmony_ci * This function does essentially the same as ssam_request_do_sync(), but 174262306a36Sopenharmony_ci * instead of dynamically allocating the request and message data buffer, it 174362306a36Sopenharmony_ci * uses the provided message data buffer and stores the (small) request struct 174462306a36Sopenharmony_ci * on the heap. 174562306a36Sopenharmony_ci * 174662306a36Sopenharmony_ci * Return: Returns the status of the request or any failure during setup. 174762306a36Sopenharmony_ci */ 174862306a36Sopenharmony_ciint ssam_request_do_sync_with_buffer(struct ssam_controller *ctrl, 174962306a36Sopenharmony_ci const struct ssam_request *spec, 175062306a36Sopenharmony_ci struct ssam_response *rsp, 175162306a36Sopenharmony_ci struct ssam_span *buf) 175262306a36Sopenharmony_ci{ 175362306a36Sopenharmony_ci struct ssam_request_sync rqst; 175462306a36Sopenharmony_ci ssize_t len; 175562306a36Sopenharmony_ci int status; 175662306a36Sopenharmony_ci 175762306a36Sopenharmony_ci status = ssam_request_sync_init(&rqst, spec->flags); 175862306a36Sopenharmony_ci if (status) 175962306a36Sopenharmony_ci return status; 176062306a36Sopenharmony_ci 176162306a36Sopenharmony_ci ssam_request_sync_set_resp(&rqst, rsp); 176262306a36Sopenharmony_ci 176362306a36Sopenharmony_ci len = ssam_request_write_data(buf, ctrl, spec); 176462306a36Sopenharmony_ci if (len < 0) 176562306a36Sopenharmony_ci return len; 176662306a36Sopenharmony_ci 176762306a36Sopenharmony_ci ssam_request_sync_set_data(&rqst, buf->ptr, len); 176862306a36Sopenharmony_ci 176962306a36Sopenharmony_ci status = ssam_request_sync_submit(ctrl, &rqst); 177062306a36Sopenharmony_ci if (!status) 177162306a36Sopenharmony_ci status = ssam_request_sync_wait(&rqst); 177262306a36Sopenharmony_ci 177362306a36Sopenharmony_ci return status; 177462306a36Sopenharmony_ci} 177562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_request_do_sync_with_buffer); 177662306a36Sopenharmony_ci 177762306a36Sopenharmony_ci 177862306a36Sopenharmony_ci/* -- Internal SAM requests. ------------------------------------------------ */ 177962306a36Sopenharmony_ci 178062306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { 178162306a36Sopenharmony_ci .target_category = SSAM_SSH_TC_SAM, 178262306a36Sopenharmony_ci .target_id = SSAM_SSH_TID_SAM, 178362306a36Sopenharmony_ci .command_id = 0x13, 178462306a36Sopenharmony_ci .instance_id = 0x00, 178562306a36Sopenharmony_ci}); 178662306a36Sopenharmony_ci 178762306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { 178862306a36Sopenharmony_ci .target_category = SSAM_SSH_TC_SAM, 178962306a36Sopenharmony_ci .target_id = SSAM_SSH_TID_SAM, 179062306a36Sopenharmony_ci .command_id = 0x15, 179162306a36Sopenharmony_ci .instance_id = 0x00, 179262306a36Sopenharmony_ci}); 179362306a36Sopenharmony_ci 179462306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { 179562306a36Sopenharmony_ci .target_category = SSAM_SSH_TC_SAM, 179662306a36Sopenharmony_ci .target_id = SSAM_SSH_TID_SAM, 179762306a36Sopenharmony_ci .command_id = 0x16, 179862306a36Sopenharmony_ci .instance_id = 0x00, 179962306a36Sopenharmony_ci}); 180062306a36Sopenharmony_ci 180162306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { 180262306a36Sopenharmony_ci .target_category = SSAM_SSH_TC_SAM, 180362306a36Sopenharmony_ci .target_id = SSAM_SSH_TID_SAM, 180462306a36Sopenharmony_ci .command_id = 0x33, 180562306a36Sopenharmony_ci .instance_id = 0x00, 180662306a36Sopenharmony_ci}); 180762306a36Sopenharmony_ci 180862306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { 180962306a36Sopenharmony_ci .target_category = SSAM_SSH_TC_SAM, 181062306a36Sopenharmony_ci .target_id = SSAM_SSH_TID_SAM, 181162306a36Sopenharmony_ci .command_id = 0x34, 181262306a36Sopenharmony_ci .instance_id = 0x00, 181362306a36Sopenharmony_ci}); 181462306a36Sopenharmony_ci 181562306a36Sopenharmony_ci/** 181662306a36Sopenharmony_ci * struct ssh_notification_params - Command payload to enable/disable SSH 181762306a36Sopenharmony_ci * notifications. 181862306a36Sopenharmony_ci * @target_category: The target category for which notifications should be 181962306a36Sopenharmony_ci * enabled/disabled. 182062306a36Sopenharmony_ci * @flags: Flags determining how notifications are being sent. 182162306a36Sopenharmony_ci * @request_id: The request ID that is used to send these notifications. 182262306a36Sopenharmony_ci * @instance_id: The specific instance in the given target category for 182362306a36Sopenharmony_ci * which notifications should be enabled. 182462306a36Sopenharmony_ci */ 182562306a36Sopenharmony_cistruct ssh_notification_params { 182662306a36Sopenharmony_ci u8 target_category; 182762306a36Sopenharmony_ci u8 flags; 182862306a36Sopenharmony_ci __le16 request_id; 182962306a36Sopenharmony_ci u8 instance_id; 183062306a36Sopenharmony_ci} __packed; 183162306a36Sopenharmony_ci 183262306a36Sopenharmony_cistatic_assert(sizeof(struct ssh_notification_params) == 5); 183362306a36Sopenharmony_ci 183462306a36Sopenharmony_cistatic int __ssam_ssh_event_request(struct ssam_controller *ctrl, 183562306a36Sopenharmony_ci struct ssam_event_registry reg, u8 cid, 183662306a36Sopenharmony_ci struct ssam_event_id id, u8 flags) 183762306a36Sopenharmony_ci{ 183862306a36Sopenharmony_ci struct ssh_notification_params params; 183962306a36Sopenharmony_ci struct ssam_request rqst; 184062306a36Sopenharmony_ci struct ssam_response result; 184162306a36Sopenharmony_ci int status; 184262306a36Sopenharmony_ci 184362306a36Sopenharmony_ci u16 rqid = ssh_tc_to_rqid(id.target_category); 184462306a36Sopenharmony_ci u8 buf = 0; 184562306a36Sopenharmony_ci 184662306a36Sopenharmony_ci /* Only allow RQIDs that lie within the event spectrum. */ 184762306a36Sopenharmony_ci if (!ssh_rqid_is_event(rqid)) 184862306a36Sopenharmony_ci return -EINVAL; 184962306a36Sopenharmony_ci 185062306a36Sopenharmony_ci params.target_category = id.target_category; 185162306a36Sopenharmony_ci params.instance_id = id.instance; 185262306a36Sopenharmony_ci params.flags = flags; 185362306a36Sopenharmony_ci put_unaligned_le16(rqid, ¶ms.request_id); 185462306a36Sopenharmony_ci 185562306a36Sopenharmony_ci rqst.target_category = reg.target_category; 185662306a36Sopenharmony_ci rqst.target_id = reg.target_id; 185762306a36Sopenharmony_ci rqst.command_id = cid; 185862306a36Sopenharmony_ci rqst.instance_id = 0x00; 185962306a36Sopenharmony_ci rqst.flags = SSAM_REQUEST_HAS_RESPONSE; 186062306a36Sopenharmony_ci rqst.length = sizeof(params); 186162306a36Sopenharmony_ci rqst.payload = (u8 *)¶ms; 186262306a36Sopenharmony_ci 186362306a36Sopenharmony_ci result.capacity = sizeof(buf); 186462306a36Sopenharmony_ci result.length = 0; 186562306a36Sopenharmony_ci result.pointer = &buf; 186662306a36Sopenharmony_ci 186762306a36Sopenharmony_ci status = ssam_retry(ssam_request_do_sync_onstack, ctrl, &rqst, &result, 186862306a36Sopenharmony_ci sizeof(params)); 186962306a36Sopenharmony_ci 187062306a36Sopenharmony_ci return status < 0 ? status : buf; 187162306a36Sopenharmony_ci} 187262306a36Sopenharmony_ci 187362306a36Sopenharmony_ci/** 187462306a36Sopenharmony_ci * ssam_ssh_event_enable() - Enable SSH event. 187562306a36Sopenharmony_ci * @ctrl: The controller for which to enable the event. 187662306a36Sopenharmony_ci * @reg: The event registry describing what request to use for enabling and 187762306a36Sopenharmony_ci * disabling the event. 187862306a36Sopenharmony_ci * @id: The event identifier. 187962306a36Sopenharmony_ci * @flags: The event flags. 188062306a36Sopenharmony_ci * 188162306a36Sopenharmony_ci * Enables the specified event on the EC. This function does not manage 188262306a36Sopenharmony_ci * reference counting of enabled events and is basically only a wrapper for 188362306a36Sopenharmony_ci * the raw EC request. If the specified event is already enabled, the EC will 188462306a36Sopenharmony_ci * ignore this request. 188562306a36Sopenharmony_ci * 188662306a36Sopenharmony_ci * Return: Returns the status of the executed SAM request (zero on success and 188762306a36Sopenharmony_ci * negative on direct failure) or %-EPROTO if the request response indicates a 188862306a36Sopenharmony_ci * failure. 188962306a36Sopenharmony_ci */ 189062306a36Sopenharmony_cistatic int ssam_ssh_event_enable(struct ssam_controller *ctrl, 189162306a36Sopenharmony_ci struct ssam_event_registry reg, 189262306a36Sopenharmony_ci struct ssam_event_id id, u8 flags) 189362306a36Sopenharmony_ci{ 189462306a36Sopenharmony_ci int status; 189562306a36Sopenharmony_ci 189662306a36Sopenharmony_ci status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); 189762306a36Sopenharmony_ci 189862306a36Sopenharmony_ci if (status < 0 && status != -EINVAL) { 189962306a36Sopenharmony_ci ssam_err(ctrl, 190062306a36Sopenharmony_ci "failed to enable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", 190162306a36Sopenharmony_ci id.target_category, id.instance, reg.target_category); 190262306a36Sopenharmony_ci } 190362306a36Sopenharmony_ci 190462306a36Sopenharmony_ci if (status > 0) { 190562306a36Sopenharmony_ci ssam_err(ctrl, 190662306a36Sopenharmony_ci "unexpected result while enabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", 190762306a36Sopenharmony_ci status, id.target_category, id.instance, reg.target_category); 190862306a36Sopenharmony_ci return -EPROTO; 190962306a36Sopenharmony_ci } 191062306a36Sopenharmony_ci 191162306a36Sopenharmony_ci return status; 191262306a36Sopenharmony_ci} 191362306a36Sopenharmony_ci 191462306a36Sopenharmony_ci/** 191562306a36Sopenharmony_ci * ssam_ssh_event_disable() - Disable SSH event. 191662306a36Sopenharmony_ci * @ctrl: The controller for which to disable the event. 191762306a36Sopenharmony_ci * @reg: The event registry describing what request to use for enabling and 191862306a36Sopenharmony_ci * disabling the event (must be same as used when enabling the event). 191962306a36Sopenharmony_ci * @id: The event identifier. 192062306a36Sopenharmony_ci * @flags: The event flags (likely ignored for disabling of events). 192162306a36Sopenharmony_ci * 192262306a36Sopenharmony_ci * Disables the specified event on the EC. This function does not manage 192362306a36Sopenharmony_ci * reference counting of enabled events and is basically only a wrapper for 192462306a36Sopenharmony_ci * the raw EC request. If the specified event is already disabled, the EC will 192562306a36Sopenharmony_ci * ignore this request. 192662306a36Sopenharmony_ci * 192762306a36Sopenharmony_ci * Return: Returns the status of the executed SAM request (zero on success and 192862306a36Sopenharmony_ci * negative on direct failure) or %-EPROTO if the request response indicates a 192962306a36Sopenharmony_ci * failure. 193062306a36Sopenharmony_ci */ 193162306a36Sopenharmony_cistatic int ssam_ssh_event_disable(struct ssam_controller *ctrl, 193262306a36Sopenharmony_ci struct ssam_event_registry reg, 193362306a36Sopenharmony_ci struct ssam_event_id id, u8 flags) 193462306a36Sopenharmony_ci{ 193562306a36Sopenharmony_ci int status; 193662306a36Sopenharmony_ci 193762306a36Sopenharmony_ci status = __ssam_ssh_event_request(ctrl, reg, reg.cid_disable, id, flags); 193862306a36Sopenharmony_ci 193962306a36Sopenharmony_ci if (status < 0 && status != -EINVAL) { 194062306a36Sopenharmony_ci ssam_err(ctrl, 194162306a36Sopenharmony_ci "failed to disable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", 194262306a36Sopenharmony_ci id.target_category, id.instance, reg.target_category); 194362306a36Sopenharmony_ci } 194462306a36Sopenharmony_ci 194562306a36Sopenharmony_ci if (status > 0) { 194662306a36Sopenharmony_ci ssam_err(ctrl, 194762306a36Sopenharmony_ci "unexpected result while disabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", 194862306a36Sopenharmony_ci status, id.target_category, id.instance, reg.target_category); 194962306a36Sopenharmony_ci return -EPROTO; 195062306a36Sopenharmony_ci } 195162306a36Sopenharmony_ci 195262306a36Sopenharmony_ci return status; 195362306a36Sopenharmony_ci} 195462306a36Sopenharmony_ci 195562306a36Sopenharmony_ci 195662306a36Sopenharmony_ci/* -- Wrappers for internal SAM requests. ----------------------------------- */ 195762306a36Sopenharmony_ci 195862306a36Sopenharmony_ci/** 195962306a36Sopenharmony_ci * ssam_get_firmware_version() - Get the SAM/EC firmware version. 196062306a36Sopenharmony_ci * @ctrl: The controller. 196162306a36Sopenharmony_ci * @version: Where to store the version number. 196262306a36Sopenharmony_ci * 196362306a36Sopenharmony_ci * Return: Returns zero on success or the status of the executed SAM request 196462306a36Sopenharmony_ci * if that request failed. 196562306a36Sopenharmony_ci */ 196662306a36Sopenharmony_ciint ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version) 196762306a36Sopenharmony_ci{ 196862306a36Sopenharmony_ci __le32 __version; 196962306a36Sopenharmony_ci int status; 197062306a36Sopenharmony_ci 197162306a36Sopenharmony_ci status = ssam_retry(ssam_ssh_get_firmware_version, ctrl, &__version); 197262306a36Sopenharmony_ci if (status) 197362306a36Sopenharmony_ci return status; 197462306a36Sopenharmony_ci 197562306a36Sopenharmony_ci *version = le32_to_cpu(__version); 197662306a36Sopenharmony_ci return 0; 197762306a36Sopenharmony_ci} 197862306a36Sopenharmony_ci 197962306a36Sopenharmony_ci/** 198062306a36Sopenharmony_ci * ssam_ctrl_notif_display_off() - Notify EC that the display has been turned 198162306a36Sopenharmony_ci * off. 198262306a36Sopenharmony_ci * @ctrl: The controller. 198362306a36Sopenharmony_ci * 198462306a36Sopenharmony_ci * Notify the EC that the display has been turned off and the driver may enter 198562306a36Sopenharmony_ci * a lower-power state. This will prevent events from being sent directly. 198662306a36Sopenharmony_ci * Rather, the EC signals an event by pulling the wakeup GPIO high for as long 198762306a36Sopenharmony_ci * as there are pending events. The events then need to be manually released, 198862306a36Sopenharmony_ci * one by one, via the GPIO callback request. All pending events accumulated 198962306a36Sopenharmony_ci * during this state can also be released by issuing the display-on 199062306a36Sopenharmony_ci * notification, e.g. via ssam_ctrl_notif_display_on(), which will also reset 199162306a36Sopenharmony_ci * the GPIO. 199262306a36Sopenharmony_ci * 199362306a36Sopenharmony_ci * On some devices, specifically ones with an integrated keyboard, the keyboard 199462306a36Sopenharmony_ci * backlight will be turned off by this call. 199562306a36Sopenharmony_ci * 199662306a36Sopenharmony_ci * This function will only send the display-off notification command if 199762306a36Sopenharmony_ci * display notifications are supported by the EC. Currently all known devices 199862306a36Sopenharmony_ci * support these notifications. 199962306a36Sopenharmony_ci * 200062306a36Sopenharmony_ci * Use ssam_ctrl_notif_display_on() to reverse the effects of this function. 200162306a36Sopenharmony_ci * 200262306a36Sopenharmony_ci * Return: Returns zero on success or if no request has been executed, the 200362306a36Sopenharmony_ci * status of the executed SAM request if that request failed, or %-EPROTO if 200462306a36Sopenharmony_ci * an unexpected response has been received. 200562306a36Sopenharmony_ci */ 200662306a36Sopenharmony_ciint ssam_ctrl_notif_display_off(struct ssam_controller *ctrl) 200762306a36Sopenharmony_ci{ 200862306a36Sopenharmony_ci int status; 200962306a36Sopenharmony_ci u8 response; 201062306a36Sopenharmony_ci 201162306a36Sopenharmony_ci ssam_dbg(ctrl, "pm: notifying display off\n"); 201262306a36Sopenharmony_ci 201362306a36Sopenharmony_ci status = ssam_retry(ssam_ssh_notif_display_off, ctrl, &response); 201462306a36Sopenharmony_ci if (status) 201562306a36Sopenharmony_ci return status; 201662306a36Sopenharmony_ci 201762306a36Sopenharmony_ci if (response != 0) { 201862306a36Sopenharmony_ci ssam_err(ctrl, "unexpected response from display-off notification: %#04x\n", 201962306a36Sopenharmony_ci response); 202062306a36Sopenharmony_ci return -EPROTO; 202162306a36Sopenharmony_ci } 202262306a36Sopenharmony_ci 202362306a36Sopenharmony_ci return 0; 202462306a36Sopenharmony_ci} 202562306a36Sopenharmony_ci 202662306a36Sopenharmony_ci/** 202762306a36Sopenharmony_ci * ssam_ctrl_notif_display_on() - Notify EC that the display has been turned on. 202862306a36Sopenharmony_ci * @ctrl: The controller. 202962306a36Sopenharmony_ci * 203062306a36Sopenharmony_ci * Notify the EC that the display has been turned back on and the driver has 203162306a36Sopenharmony_ci * exited its lower-power state. This notification is the counterpart to the 203262306a36Sopenharmony_ci * display-off notification sent via ssam_ctrl_notif_display_off() and will 203362306a36Sopenharmony_ci * reverse its effects, including resetting events to their default behavior. 203462306a36Sopenharmony_ci * 203562306a36Sopenharmony_ci * This function will only send the display-on notification command if display 203662306a36Sopenharmony_ci * notifications are supported by the EC. Currently all known devices support 203762306a36Sopenharmony_ci * these notifications. 203862306a36Sopenharmony_ci * 203962306a36Sopenharmony_ci * See ssam_ctrl_notif_display_off() for more details. 204062306a36Sopenharmony_ci * 204162306a36Sopenharmony_ci * Return: Returns zero on success or if no request has been executed, the 204262306a36Sopenharmony_ci * status of the executed SAM request if that request failed, or %-EPROTO if 204362306a36Sopenharmony_ci * an unexpected response has been received. 204462306a36Sopenharmony_ci */ 204562306a36Sopenharmony_ciint ssam_ctrl_notif_display_on(struct ssam_controller *ctrl) 204662306a36Sopenharmony_ci{ 204762306a36Sopenharmony_ci int status; 204862306a36Sopenharmony_ci u8 response; 204962306a36Sopenharmony_ci 205062306a36Sopenharmony_ci ssam_dbg(ctrl, "pm: notifying display on\n"); 205162306a36Sopenharmony_ci 205262306a36Sopenharmony_ci status = ssam_retry(ssam_ssh_notif_display_on, ctrl, &response); 205362306a36Sopenharmony_ci if (status) 205462306a36Sopenharmony_ci return status; 205562306a36Sopenharmony_ci 205662306a36Sopenharmony_ci if (response != 0) { 205762306a36Sopenharmony_ci ssam_err(ctrl, "unexpected response from display-on notification: %#04x\n", 205862306a36Sopenharmony_ci response); 205962306a36Sopenharmony_ci return -EPROTO; 206062306a36Sopenharmony_ci } 206162306a36Sopenharmony_ci 206262306a36Sopenharmony_ci return 0; 206362306a36Sopenharmony_ci} 206462306a36Sopenharmony_ci 206562306a36Sopenharmony_ci/** 206662306a36Sopenharmony_ci * ssam_ctrl_notif_d0_exit() - Notify EC that the driver/device exits the D0 206762306a36Sopenharmony_ci * power state. 206862306a36Sopenharmony_ci * @ctrl: The controller 206962306a36Sopenharmony_ci * 207062306a36Sopenharmony_ci * Notifies the EC that the driver prepares to exit the D0 power state in 207162306a36Sopenharmony_ci * favor of a lower-power state. Exact effects of this function related to the 207262306a36Sopenharmony_ci * EC are currently unknown. 207362306a36Sopenharmony_ci * 207462306a36Sopenharmony_ci * This function will only send the D0-exit notification command if D0-state 207562306a36Sopenharmony_ci * notifications are supported by the EC. Only newer Surface generations 207662306a36Sopenharmony_ci * support these notifications. 207762306a36Sopenharmony_ci * 207862306a36Sopenharmony_ci * Use ssam_ctrl_notif_d0_entry() to reverse the effects of this function. 207962306a36Sopenharmony_ci * 208062306a36Sopenharmony_ci * Return: Returns zero on success or if no request has been executed, the 208162306a36Sopenharmony_ci * status of the executed SAM request if that request failed, or %-EPROTO if 208262306a36Sopenharmony_ci * an unexpected response has been received. 208362306a36Sopenharmony_ci */ 208462306a36Sopenharmony_ciint ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl) 208562306a36Sopenharmony_ci{ 208662306a36Sopenharmony_ci int status; 208762306a36Sopenharmony_ci u8 response; 208862306a36Sopenharmony_ci 208962306a36Sopenharmony_ci if (!ctrl->caps.d3_closes_handle) 209062306a36Sopenharmony_ci return 0; 209162306a36Sopenharmony_ci 209262306a36Sopenharmony_ci ssam_dbg(ctrl, "pm: notifying D0 exit\n"); 209362306a36Sopenharmony_ci 209462306a36Sopenharmony_ci status = ssam_retry(ssam_ssh_notif_d0_exit, ctrl, &response); 209562306a36Sopenharmony_ci if (status) 209662306a36Sopenharmony_ci return status; 209762306a36Sopenharmony_ci 209862306a36Sopenharmony_ci if (response != 0) { 209962306a36Sopenharmony_ci ssam_err(ctrl, "unexpected response from D0-exit notification: %#04x\n", 210062306a36Sopenharmony_ci response); 210162306a36Sopenharmony_ci return -EPROTO; 210262306a36Sopenharmony_ci } 210362306a36Sopenharmony_ci 210462306a36Sopenharmony_ci return 0; 210562306a36Sopenharmony_ci} 210662306a36Sopenharmony_ci 210762306a36Sopenharmony_ci/** 210862306a36Sopenharmony_ci * ssam_ctrl_notif_d0_entry() - Notify EC that the driver/device enters the D0 210962306a36Sopenharmony_ci * power state. 211062306a36Sopenharmony_ci * @ctrl: The controller 211162306a36Sopenharmony_ci * 211262306a36Sopenharmony_ci * Notifies the EC that the driver has exited a lower-power state and entered 211362306a36Sopenharmony_ci * the D0 power state. Exact effects of this function related to the EC are 211462306a36Sopenharmony_ci * currently unknown. 211562306a36Sopenharmony_ci * 211662306a36Sopenharmony_ci * This function will only send the D0-entry notification command if D0-state 211762306a36Sopenharmony_ci * notifications are supported by the EC. Only newer Surface generations 211862306a36Sopenharmony_ci * support these notifications. 211962306a36Sopenharmony_ci * 212062306a36Sopenharmony_ci * See ssam_ctrl_notif_d0_exit() for more details. 212162306a36Sopenharmony_ci * 212262306a36Sopenharmony_ci * Return: Returns zero on success or if no request has been executed, the 212362306a36Sopenharmony_ci * status of the executed SAM request if that request failed, or %-EPROTO if 212462306a36Sopenharmony_ci * an unexpected response has been received. 212562306a36Sopenharmony_ci */ 212662306a36Sopenharmony_ciint ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) 212762306a36Sopenharmony_ci{ 212862306a36Sopenharmony_ci int status; 212962306a36Sopenharmony_ci u8 response; 213062306a36Sopenharmony_ci 213162306a36Sopenharmony_ci if (!ctrl->caps.d3_closes_handle) 213262306a36Sopenharmony_ci return 0; 213362306a36Sopenharmony_ci 213462306a36Sopenharmony_ci ssam_dbg(ctrl, "pm: notifying D0 entry\n"); 213562306a36Sopenharmony_ci 213662306a36Sopenharmony_ci status = ssam_retry(ssam_ssh_notif_d0_entry, ctrl, &response); 213762306a36Sopenharmony_ci if (status) 213862306a36Sopenharmony_ci return status; 213962306a36Sopenharmony_ci 214062306a36Sopenharmony_ci if (response != 0) { 214162306a36Sopenharmony_ci ssam_err(ctrl, "unexpected response from D0-entry notification: %#04x\n", 214262306a36Sopenharmony_ci response); 214362306a36Sopenharmony_ci return -EPROTO; 214462306a36Sopenharmony_ci } 214562306a36Sopenharmony_ci 214662306a36Sopenharmony_ci return 0; 214762306a36Sopenharmony_ci} 214862306a36Sopenharmony_ci 214962306a36Sopenharmony_ci 215062306a36Sopenharmony_ci/* -- Top-level event registry interface. ----------------------------------- */ 215162306a36Sopenharmony_ci 215262306a36Sopenharmony_ci/** 215362306a36Sopenharmony_ci * ssam_nf_refcount_enable() - Enable event for reference count entry if it has 215462306a36Sopenharmony_ci * not already been enabled. 215562306a36Sopenharmony_ci * @ctrl: The controller to enable the event on. 215662306a36Sopenharmony_ci * @entry: The reference count entry for the event to be enabled. 215762306a36Sopenharmony_ci * @flags: The flags used for enabling the event on the EC. 215862306a36Sopenharmony_ci * 215962306a36Sopenharmony_ci * Enable the event associated with the given reference count entry if the 216062306a36Sopenharmony_ci * reference count equals one, i.e. the event has not previously been enabled. 216162306a36Sopenharmony_ci * If the event has already been enabled (i.e. reference count not equal to 216262306a36Sopenharmony_ci * one), check that the flags used for enabling match and warn about this if 216362306a36Sopenharmony_ci * they do not. 216462306a36Sopenharmony_ci * 216562306a36Sopenharmony_ci * This does not modify the reference count itself, which is done with 216662306a36Sopenharmony_ci * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). 216762306a36Sopenharmony_ci * 216862306a36Sopenharmony_ci * Note: ``nf->lock`` must be held when calling this function. 216962306a36Sopenharmony_ci * 217062306a36Sopenharmony_ci * Return: Returns zero on success. If the event is enabled by this call, 217162306a36Sopenharmony_ci * returns the status of the event-enable EC command. 217262306a36Sopenharmony_ci */ 217362306a36Sopenharmony_cistatic int ssam_nf_refcount_enable(struct ssam_controller *ctrl, 217462306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry, u8 flags) 217562306a36Sopenharmony_ci{ 217662306a36Sopenharmony_ci const struct ssam_event_registry reg = entry->key.reg; 217762306a36Sopenharmony_ci const struct ssam_event_id id = entry->key.id; 217862306a36Sopenharmony_ci struct ssam_nf *nf = &ctrl->cplt.event.notif; 217962306a36Sopenharmony_ci int status; 218062306a36Sopenharmony_ci 218162306a36Sopenharmony_ci lockdep_assert_held(&nf->lock); 218262306a36Sopenharmony_ci 218362306a36Sopenharmony_ci ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", 218462306a36Sopenharmony_ci reg.target_category, id.target_category, id.instance, entry->refcount); 218562306a36Sopenharmony_ci 218662306a36Sopenharmony_ci if (entry->refcount == 1) { 218762306a36Sopenharmony_ci status = ssam_ssh_event_enable(ctrl, reg, id, flags); 218862306a36Sopenharmony_ci if (status) 218962306a36Sopenharmony_ci return status; 219062306a36Sopenharmony_ci 219162306a36Sopenharmony_ci entry->flags = flags; 219262306a36Sopenharmony_ci 219362306a36Sopenharmony_ci } else if (entry->flags != flags) { 219462306a36Sopenharmony_ci ssam_warn(ctrl, 219562306a36Sopenharmony_ci "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", 219662306a36Sopenharmony_ci flags, entry->flags, reg.target_category, id.target_category, 219762306a36Sopenharmony_ci id.instance); 219862306a36Sopenharmony_ci } 219962306a36Sopenharmony_ci 220062306a36Sopenharmony_ci return 0; 220162306a36Sopenharmony_ci} 220262306a36Sopenharmony_ci 220362306a36Sopenharmony_ci/** 220462306a36Sopenharmony_ci * ssam_nf_refcount_disable_free() - Disable event for reference count entry if 220562306a36Sopenharmony_ci * it is no longer in use and free the corresponding entry. 220662306a36Sopenharmony_ci * @ctrl: The controller to disable the event on. 220762306a36Sopenharmony_ci * @entry: The reference count entry for the event to be disabled. 220862306a36Sopenharmony_ci * @flags: The flags used for enabling the event on the EC. 220962306a36Sopenharmony_ci * @ec: Flag specifying if the event should actually be disabled on the EC. 221062306a36Sopenharmony_ci * 221162306a36Sopenharmony_ci * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the 221262306a36Sopenharmony_ci * event is no longer requested by any client), the specified event will be 221362306a36Sopenharmony_ci * disabled on the EC via the corresponding request. 221462306a36Sopenharmony_ci * 221562306a36Sopenharmony_ci * If ``ec`` equals ``false``, no request will be sent to the EC and the event 221662306a36Sopenharmony_ci * can be considered in a detached state (i.e. no longer used but still 221762306a36Sopenharmony_ci * enabled). Disabling an event via this method may be required for 221862306a36Sopenharmony_ci * hot-removable devices, where event disable requests may time out after the 221962306a36Sopenharmony_ci * device has been physically removed. 222062306a36Sopenharmony_ci * 222162306a36Sopenharmony_ci * In both cases, if the reference count equals zero, the corresponding 222262306a36Sopenharmony_ci * reference count entry will be freed. The reference count entry must not be 222362306a36Sopenharmony_ci * used any more after a call to this function. 222462306a36Sopenharmony_ci * 222562306a36Sopenharmony_ci * Also checks if the flags used for disabling the event match the flags used 222662306a36Sopenharmony_ci * for enabling the event and warns if they do not (regardless of reference 222762306a36Sopenharmony_ci * count). 222862306a36Sopenharmony_ci * 222962306a36Sopenharmony_ci * This does not modify the reference count itself, which is done with 223062306a36Sopenharmony_ci * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). 223162306a36Sopenharmony_ci * 223262306a36Sopenharmony_ci * Note: ``nf->lock`` must be held when calling this function. 223362306a36Sopenharmony_ci * 223462306a36Sopenharmony_ci * Return: Returns zero on success. If the event is disabled by this call, 223562306a36Sopenharmony_ci * returns the status of the event-enable EC command. 223662306a36Sopenharmony_ci */ 223762306a36Sopenharmony_cistatic int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, 223862306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) 223962306a36Sopenharmony_ci{ 224062306a36Sopenharmony_ci const struct ssam_event_registry reg = entry->key.reg; 224162306a36Sopenharmony_ci const struct ssam_event_id id = entry->key.id; 224262306a36Sopenharmony_ci struct ssam_nf *nf = &ctrl->cplt.event.notif; 224362306a36Sopenharmony_ci int status = 0; 224462306a36Sopenharmony_ci 224562306a36Sopenharmony_ci lockdep_assert_held(&nf->lock); 224662306a36Sopenharmony_ci 224762306a36Sopenharmony_ci ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", 224862306a36Sopenharmony_ci ec ? "disabling" : "detaching", reg.target_category, id.target_category, 224962306a36Sopenharmony_ci id.instance, entry->refcount); 225062306a36Sopenharmony_ci 225162306a36Sopenharmony_ci if (entry->flags != flags) { 225262306a36Sopenharmony_ci ssam_warn(ctrl, 225362306a36Sopenharmony_ci "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", 225462306a36Sopenharmony_ci flags, entry->flags, reg.target_category, id.target_category, 225562306a36Sopenharmony_ci id.instance); 225662306a36Sopenharmony_ci } 225762306a36Sopenharmony_ci 225862306a36Sopenharmony_ci if (ec && entry->refcount == 0) { 225962306a36Sopenharmony_ci status = ssam_ssh_event_disable(ctrl, reg, id, flags); 226062306a36Sopenharmony_ci kfree(entry); 226162306a36Sopenharmony_ci } 226262306a36Sopenharmony_ci 226362306a36Sopenharmony_ci return status; 226462306a36Sopenharmony_ci} 226562306a36Sopenharmony_ci 226662306a36Sopenharmony_ci/** 226762306a36Sopenharmony_ci * ssam_notifier_register() - Register an event notifier. 226862306a36Sopenharmony_ci * @ctrl: The controller to register the notifier on. 226962306a36Sopenharmony_ci * @n: The event notifier to register. 227062306a36Sopenharmony_ci * 227162306a36Sopenharmony_ci * Register an event notifier. Increment the usage counter of the associated 227262306a36Sopenharmony_ci * SAM event if the notifier is not marked as an observer. If the event is not 227362306a36Sopenharmony_ci * marked as an observer and is currently not enabled, it will be enabled 227462306a36Sopenharmony_ci * during this call. If the notifier is marked as an observer, no attempt will 227562306a36Sopenharmony_ci * be made at enabling any event and no reference count will be modified. 227662306a36Sopenharmony_ci * 227762306a36Sopenharmony_ci * Notifiers marked as observers do not need to be associated with one specific 227862306a36Sopenharmony_ci * event, i.e. as long as no event matching is performed, only the event target 227962306a36Sopenharmony_ci * category needs to be set. 228062306a36Sopenharmony_ci * 228162306a36Sopenharmony_ci * Return: Returns zero on success, %-ENOSPC if there have already been 228262306a36Sopenharmony_ci * %INT_MAX notifiers for the event ID/type associated with the notifier block 228362306a36Sopenharmony_ci * registered, %-ENOMEM if the corresponding event entry could not be 228462306a36Sopenharmony_ci * allocated. If this is the first time that a notifier block is registered 228562306a36Sopenharmony_ci * for the specific associated event, returns the status of the event-enable 228662306a36Sopenharmony_ci * EC-command. 228762306a36Sopenharmony_ci */ 228862306a36Sopenharmony_ciint ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) 228962306a36Sopenharmony_ci{ 229062306a36Sopenharmony_ci u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); 229162306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry = NULL; 229262306a36Sopenharmony_ci struct ssam_nf_head *nf_head; 229362306a36Sopenharmony_ci struct ssam_nf *nf; 229462306a36Sopenharmony_ci int status; 229562306a36Sopenharmony_ci 229662306a36Sopenharmony_ci if (!ssh_rqid_is_event(rqid)) 229762306a36Sopenharmony_ci return -EINVAL; 229862306a36Sopenharmony_ci 229962306a36Sopenharmony_ci nf = &ctrl->cplt.event.notif; 230062306a36Sopenharmony_ci nf_head = &nf->head[ssh_rqid_to_event(rqid)]; 230162306a36Sopenharmony_ci 230262306a36Sopenharmony_ci mutex_lock(&nf->lock); 230362306a36Sopenharmony_ci 230462306a36Sopenharmony_ci if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { 230562306a36Sopenharmony_ci entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); 230662306a36Sopenharmony_ci if (IS_ERR(entry)) { 230762306a36Sopenharmony_ci mutex_unlock(&nf->lock); 230862306a36Sopenharmony_ci return PTR_ERR(entry); 230962306a36Sopenharmony_ci } 231062306a36Sopenharmony_ci } 231162306a36Sopenharmony_ci 231262306a36Sopenharmony_ci status = ssam_nfblk_insert(nf_head, &n->base); 231362306a36Sopenharmony_ci if (status) { 231462306a36Sopenharmony_ci if (entry) 231562306a36Sopenharmony_ci ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); 231662306a36Sopenharmony_ci 231762306a36Sopenharmony_ci mutex_unlock(&nf->lock); 231862306a36Sopenharmony_ci return status; 231962306a36Sopenharmony_ci } 232062306a36Sopenharmony_ci 232162306a36Sopenharmony_ci if (entry) { 232262306a36Sopenharmony_ci status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); 232362306a36Sopenharmony_ci if (status) { 232462306a36Sopenharmony_ci ssam_nfblk_remove(&n->base); 232562306a36Sopenharmony_ci ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); 232662306a36Sopenharmony_ci mutex_unlock(&nf->lock); 232762306a36Sopenharmony_ci synchronize_srcu(&nf_head->srcu); 232862306a36Sopenharmony_ci return status; 232962306a36Sopenharmony_ci } 233062306a36Sopenharmony_ci } 233162306a36Sopenharmony_ci 233262306a36Sopenharmony_ci mutex_unlock(&nf->lock); 233362306a36Sopenharmony_ci return 0; 233462306a36Sopenharmony_ci} 233562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_notifier_register); 233662306a36Sopenharmony_ci 233762306a36Sopenharmony_ci/** 233862306a36Sopenharmony_ci * __ssam_notifier_unregister() - Unregister an event notifier. 233962306a36Sopenharmony_ci * @ctrl: The controller the notifier has been registered on. 234062306a36Sopenharmony_ci * @n: The event notifier to unregister. 234162306a36Sopenharmony_ci * @disable: Whether to disable the corresponding event on the EC. 234262306a36Sopenharmony_ci * 234362306a36Sopenharmony_ci * Unregister an event notifier. Decrement the usage counter of the associated 234462306a36Sopenharmony_ci * SAM event if the notifier is not marked as an observer. If the usage counter 234562306a36Sopenharmony_ci * reaches zero and ``disable`` equals ``true``, the event will be disabled. 234662306a36Sopenharmony_ci * 234762306a36Sopenharmony_ci * Useful for hot-removable devices, where communication may fail once the 234862306a36Sopenharmony_ci * device has been physically removed. In that case, specifying ``disable`` as 234962306a36Sopenharmony_ci * ``false`` avoids communication with the EC. 235062306a36Sopenharmony_ci * 235162306a36Sopenharmony_ci * Return: Returns zero on success, %-ENOENT if the given notifier block has 235262306a36Sopenharmony_ci * not been registered on the controller. If the given notifier block was the 235362306a36Sopenharmony_ci * last one associated with its specific event, returns the status of the 235462306a36Sopenharmony_ci * event-disable EC-command. 235562306a36Sopenharmony_ci */ 235662306a36Sopenharmony_ciint __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, 235762306a36Sopenharmony_ci bool disable) 235862306a36Sopenharmony_ci{ 235962306a36Sopenharmony_ci u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); 236062306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry; 236162306a36Sopenharmony_ci struct ssam_nf_head *nf_head; 236262306a36Sopenharmony_ci struct ssam_nf *nf; 236362306a36Sopenharmony_ci int status = 0; 236462306a36Sopenharmony_ci 236562306a36Sopenharmony_ci if (!ssh_rqid_is_event(rqid)) 236662306a36Sopenharmony_ci return -EINVAL; 236762306a36Sopenharmony_ci 236862306a36Sopenharmony_ci nf = &ctrl->cplt.event.notif; 236962306a36Sopenharmony_ci nf_head = &nf->head[ssh_rqid_to_event(rqid)]; 237062306a36Sopenharmony_ci 237162306a36Sopenharmony_ci mutex_lock(&nf->lock); 237262306a36Sopenharmony_ci 237362306a36Sopenharmony_ci if (!ssam_nfblk_find(nf_head, &n->base)) { 237462306a36Sopenharmony_ci mutex_unlock(&nf->lock); 237562306a36Sopenharmony_ci return -ENOENT; 237662306a36Sopenharmony_ci } 237762306a36Sopenharmony_ci 237862306a36Sopenharmony_ci /* 237962306a36Sopenharmony_ci * If this is an observer notifier, do not attempt to disable the 238062306a36Sopenharmony_ci * event, just remove it. 238162306a36Sopenharmony_ci */ 238262306a36Sopenharmony_ci if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { 238362306a36Sopenharmony_ci entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); 238462306a36Sopenharmony_ci if (WARN_ON(!entry)) { 238562306a36Sopenharmony_ci /* 238662306a36Sopenharmony_ci * If this does not return an entry, there's a logic 238762306a36Sopenharmony_ci * error somewhere: The notifier block is registered, 238862306a36Sopenharmony_ci * but the event refcount entry is not there. Remove 238962306a36Sopenharmony_ci * the notifier block anyways. 239062306a36Sopenharmony_ci */ 239162306a36Sopenharmony_ci status = -ENOENT; 239262306a36Sopenharmony_ci goto remove; 239362306a36Sopenharmony_ci } 239462306a36Sopenharmony_ci 239562306a36Sopenharmony_ci status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); 239662306a36Sopenharmony_ci } 239762306a36Sopenharmony_ci 239862306a36Sopenharmony_ciremove: 239962306a36Sopenharmony_ci ssam_nfblk_remove(&n->base); 240062306a36Sopenharmony_ci mutex_unlock(&nf->lock); 240162306a36Sopenharmony_ci synchronize_srcu(&nf_head->srcu); 240262306a36Sopenharmony_ci 240362306a36Sopenharmony_ci return status; 240462306a36Sopenharmony_ci} 240562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__ssam_notifier_unregister); 240662306a36Sopenharmony_ci 240762306a36Sopenharmony_ci/** 240862306a36Sopenharmony_ci * ssam_controller_event_enable() - Enable the specified event. 240962306a36Sopenharmony_ci * @ctrl: The controller to enable the event for. 241062306a36Sopenharmony_ci * @reg: The event registry to use for enabling the event. 241162306a36Sopenharmony_ci * @id: The event ID specifying the event to be enabled. 241262306a36Sopenharmony_ci * @flags: The SAM event flags used for enabling the event. 241362306a36Sopenharmony_ci * 241462306a36Sopenharmony_ci * Increment the event reference count of the specified event. If the event has 241562306a36Sopenharmony_ci * not been enabled previously, it will be enabled by this call. 241662306a36Sopenharmony_ci * 241762306a36Sopenharmony_ci * Note: In general, ssam_notifier_register() with a non-observer notifier 241862306a36Sopenharmony_ci * should be preferred for enabling/disabling events, as this will guarantee 241962306a36Sopenharmony_ci * proper ordering and event forwarding in case of errors during event 242062306a36Sopenharmony_ci * enabling/disabling. 242162306a36Sopenharmony_ci * 242262306a36Sopenharmony_ci * Return: Returns zero on success, %-ENOSPC if the reference count for the 242362306a36Sopenharmony_ci * specified event has reached its maximum, %-ENOMEM if the corresponding event 242462306a36Sopenharmony_ci * entry could not be allocated. If this is the first time that this event has 242562306a36Sopenharmony_ci * been enabled (i.e. the reference count was incremented from zero to one by 242662306a36Sopenharmony_ci * this call), returns the status of the event-enable EC-command. 242762306a36Sopenharmony_ci */ 242862306a36Sopenharmony_ciint ssam_controller_event_enable(struct ssam_controller *ctrl, 242962306a36Sopenharmony_ci struct ssam_event_registry reg, 243062306a36Sopenharmony_ci struct ssam_event_id id, u8 flags) 243162306a36Sopenharmony_ci{ 243262306a36Sopenharmony_ci u16 rqid = ssh_tc_to_rqid(id.target_category); 243362306a36Sopenharmony_ci struct ssam_nf *nf = &ctrl->cplt.event.notif; 243462306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry; 243562306a36Sopenharmony_ci int status; 243662306a36Sopenharmony_ci 243762306a36Sopenharmony_ci if (!ssh_rqid_is_event(rqid)) 243862306a36Sopenharmony_ci return -EINVAL; 243962306a36Sopenharmony_ci 244062306a36Sopenharmony_ci mutex_lock(&nf->lock); 244162306a36Sopenharmony_ci 244262306a36Sopenharmony_ci entry = ssam_nf_refcount_inc(nf, reg, id); 244362306a36Sopenharmony_ci if (IS_ERR(entry)) { 244462306a36Sopenharmony_ci mutex_unlock(&nf->lock); 244562306a36Sopenharmony_ci return PTR_ERR(entry); 244662306a36Sopenharmony_ci } 244762306a36Sopenharmony_ci 244862306a36Sopenharmony_ci status = ssam_nf_refcount_enable(ctrl, entry, flags); 244962306a36Sopenharmony_ci if (status) { 245062306a36Sopenharmony_ci ssam_nf_refcount_dec_free(nf, reg, id); 245162306a36Sopenharmony_ci mutex_unlock(&nf->lock); 245262306a36Sopenharmony_ci return status; 245362306a36Sopenharmony_ci } 245462306a36Sopenharmony_ci 245562306a36Sopenharmony_ci mutex_unlock(&nf->lock); 245662306a36Sopenharmony_ci return 0; 245762306a36Sopenharmony_ci} 245862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_controller_event_enable); 245962306a36Sopenharmony_ci 246062306a36Sopenharmony_ci/** 246162306a36Sopenharmony_ci * ssam_controller_event_disable() - Disable the specified event. 246262306a36Sopenharmony_ci * @ctrl: The controller to disable the event for. 246362306a36Sopenharmony_ci * @reg: The event registry to use for disabling the event. 246462306a36Sopenharmony_ci * @id: The event ID specifying the event to be disabled. 246562306a36Sopenharmony_ci * @flags: The flags used when enabling the event. 246662306a36Sopenharmony_ci * 246762306a36Sopenharmony_ci * Decrement the reference count of the specified event. If the reference count 246862306a36Sopenharmony_ci * reaches zero, the event will be disabled. 246962306a36Sopenharmony_ci * 247062306a36Sopenharmony_ci * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a 247162306a36Sopenharmony_ci * non-observer notifier should be preferred for enabling/disabling events, as 247262306a36Sopenharmony_ci * this will guarantee proper ordering and event forwarding in case of errors 247362306a36Sopenharmony_ci * during event enabling/disabling. 247462306a36Sopenharmony_ci * 247562306a36Sopenharmony_ci * Return: Returns zero on success, %-ENOENT if the given event has not been 247662306a36Sopenharmony_ci * enabled on the controller. If the reference count of the event reaches zero 247762306a36Sopenharmony_ci * during this call, returns the status of the event-disable EC-command. 247862306a36Sopenharmony_ci */ 247962306a36Sopenharmony_ciint ssam_controller_event_disable(struct ssam_controller *ctrl, 248062306a36Sopenharmony_ci struct ssam_event_registry reg, 248162306a36Sopenharmony_ci struct ssam_event_id id, u8 flags) 248262306a36Sopenharmony_ci{ 248362306a36Sopenharmony_ci u16 rqid = ssh_tc_to_rqid(id.target_category); 248462306a36Sopenharmony_ci struct ssam_nf *nf = &ctrl->cplt.event.notif; 248562306a36Sopenharmony_ci struct ssam_nf_refcount_entry *entry; 248662306a36Sopenharmony_ci int status; 248762306a36Sopenharmony_ci 248862306a36Sopenharmony_ci if (!ssh_rqid_is_event(rqid)) 248962306a36Sopenharmony_ci return -EINVAL; 249062306a36Sopenharmony_ci 249162306a36Sopenharmony_ci mutex_lock(&nf->lock); 249262306a36Sopenharmony_ci 249362306a36Sopenharmony_ci entry = ssam_nf_refcount_dec(nf, reg, id); 249462306a36Sopenharmony_ci if (!entry) { 249562306a36Sopenharmony_ci mutex_unlock(&nf->lock); 249662306a36Sopenharmony_ci return -ENOENT; 249762306a36Sopenharmony_ci } 249862306a36Sopenharmony_ci 249962306a36Sopenharmony_ci status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); 250062306a36Sopenharmony_ci 250162306a36Sopenharmony_ci mutex_unlock(&nf->lock); 250262306a36Sopenharmony_ci return status; 250362306a36Sopenharmony_ci} 250462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_controller_event_disable); 250562306a36Sopenharmony_ci 250662306a36Sopenharmony_ci/** 250762306a36Sopenharmony_ci * ssam_notifier_disable_registered() - Disable events for all registered 250862306a36Sopenharmony_ci * notifiers. 250962306a36Sopenharmony_ci * @ctrl: The controller for which to disable the notifiers/events. 251062306a36Sopenharmony_ci * 251162306a36Sopenharmony_ci * Disables events for all currently registered notifiers. In case of an error 251262306a36Sopenharmony_ci * (EC command failing), all previously disabled events will be restored and 251362306a36Sopenharmony_ci * the error code returned. 251462306a36Sopenharmony_ci * 251562306a36Sopenharmony_ci * This function is intended to disable all events prior to hibernation entry. 251662306a36Sopenharmony_ci * See ssam_notifier_restore_registered() to restore/re-enable all events 251762306a36Sopenharmony_ci * disabled with this function. 251862306a36Sopenharmony_ci * 251962306a36Sopenharmony_ci * Note that this function will not disable events for notifiers registered 252062306a36Sopenharmony_ci * after calling this function. It should thus be made sure that no new 252162306a36Sopenharmony_ci * notifiers are going to be added after this call and before the corresponding 252262306a36Sopenharmony_ci * call to ssam_notifier_restore_registered(). 252362306a36Sopenharmony_ci * 252462306a36Sopenharmony_ci * Return: Returns zero on success. In case of failure returns the error code 252562306a36Sopenharmony_ci * returned by the failed EC command to disable an event. 252662306a36Sopenharmony_ci */ 252762306a36Sopenharmony_ciint ssam_notifier_disable_registered(struct ssam_controller *ctrl) 252862306a36Sopenharmony_ci{ 252962306a36Sopenharmony_ci struct ssam_nf *nf = &ctrl->cplt.event.notif; 253062306a36Sopenharmony_ci struct rb_node *n; 253162306a36Sopenharmony_ci int status; 253262306a36Sopenharmony_ci 253362306a36Sopenharmony_ci mutex_lock(&nf->lock); 253462306a36Sopenharmony_ci for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { 253562306a36Sopenharmony_ci struct ssam_nf_refcount_entry *e; 253662306a36Sopenharmony_ci 253762306a36Sopenharmony_ci e = rb_entry(n, struct ssam_nf_refcount_entry, node); 253862306a36Sopenharmony_ci status = ssam_ssh_event_disable(ctrl, e->key.reg, 253962306a36Sopenharmony_ci e->key.id, e->flags); 254062306a36Sopenharmony_ci if (status) 254162306a36Sopenharmony_ci goto err; 254262306a36Sopenharmony_ci } 254362306a36Sopenharmony_ci mutex_unlock(&nf->lock); 254462306a36Sopenharmony_ci 254562306a36Sopenharmony_ci return 0; 254662306a36Sopenharmony_ci 254762306a36Sopenharmony_cierr: 254862306a36Sopenharmony_ci for (n = rb_prev(n); n; n = rb_prev(n)) { 254962306a36Sopenharmony_ci struct ssam_nf_refcount_entry *e; 255062306a36Sopenharmony_ci 255162306a36Sopenharmony_ci e = rb_entry(n, struct ssam_nf_refcount_entry, node); 255262306a36Sopenharmony_ci ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); 255362306a36Sopenharmony_ci } 255462306a36Sopenharmony_ci mutex_unlock(&nf->lock); 255562306a36Sopenharmony_ci 255662306a36Sopenharmony_ci return status; 255762306a36Sopenharmony_ci} 255862306a36Sopenharmony_ci 255962306a36Sopenharmony_ci/** 256062306a36Sopenharmony_ci * ssam_notifier_restore_registered() - Restore/re-enable events for all 256162306a36Sopenharmony_ci * registered notifiers. 256262306a36Sopenharmony_ci * @ctrl: The controller for which to restore the notifiers/events. 256362306a36Sopenharmony_ci * 256462306a36Sopenharmony_ci * Restores/re-enables all events for which notifiers have been registered on 256562306a36Sopenharmony_ci * the given controller. In case of a failure, the error is logged and the 256662306a36Sopenharmony_ci * function continues to try and enable the remaining events. 256762306a36Sopenharmony_ci * 256862306a36Sopenharmony_ci * This function is intended to restore/re-enable all registered events after 256962306a36Sopenharmony_ci * hibernation. See ssam_notifier_disable_registered() for the counter part 257062306a36Sopenharmony_ci * disabling the events and more details. 257162306a36Sopenharmony_ci */ 257262306a36Sopenharmony_civoid ssam_notifier_restore_registered(struct ssam_controller *ctrl) 257362306a36Sopenharmony_ci{ 257462306a36Sopenharmony_ci struct ssam_nf *nf = &ctrl->cplt.event.notif; 257562306a36Sopenharmony_ci struct rb_node *n; 257662306a36Sopenharmony_ci 257762306a36Sopenharmony_ci mutex_lock(&nf->lock); 257862306a36Sopenharmony_ci for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { 257962306a36Sopenharmony_ci struct ssam_nf_refcount_entry *e; 258062306a36Sopenharmony_ci 258162306a36Sopenharmony_ci e = rb_entry(n, struct ssam_nf_refcount_entry, node); 258262306a36Sopenharmony_ci 258362306a36Sopenharmony_ci /* Ignore errors, will get logged in call. */ 258462306a36Sopenharmony_ci ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); 258562306a36Sopenharmony_ci } 258662306a36Sopenharmony_ci mutex_unlock(&nf->lock); 258762306a36Sopenharmony_ci} 258862306a36Sopenharmony_ci 258962306a36Sopenharmony_ci/** 259062306a36Sopenharmony_ci * ssam_notifier_is_empty() - Check if there are any registered notifiers. 259162306a36Sopenharmony_ci * @ctrl: The controller to check on. 259262306a36Sopenharmony_ci * 259362306a36Sopenharmony_ci * Return: Returns %true if there are currently no notifiers registered on the 259462306a36Sopenharmony_ci * controller, %false otherwise. 259562306a36Sopenharmony_ci */ 259662306a36Sopenharmony_cistatic bool ssam_notifier_is_empty(struct ssam_controller *ctrl) 259762306a36Sopenharmony_ci{ 259862306a36Sopenharmony_ci struct ssam_nf *nf = &ctrl->cplt.event.notif; 259962306a36Sopenharmony_ci bool result; 260062306a36Sopenharmony_ci 260162306a36Sopenharmony_ci mutex_lock(&nf->lock); 260262306a36Sopenharmony_ci result = ssam_nf_refcount_empty(nf); 260362306a36Sopenharmony_ci mutex_unlock(&nf->lock); 260462306a36Sopenharmony_ci 260562306a36Sopenharmony_ci return result; 260662306a36Sopenharmony_ci} 260762306a36Sopenharmony_ci 260862306a36Sopenharmony_ci/** 260962306a36Sopenharmony_ci * ssam_notifier_unregister_all() - Unregister all currently registered 261062306a36Sopenharmony_ci * notifiers. 261162306a36Sopenharmony_ci * @ctrl: The controller to unregister the notifiers on. 261262306a36Sopenharmony_ci * 261362306a36Sopenharmony_ci * Unregisters all currently registered notifiers. This function is used to 261462306a36Sopenharmony_ci * ensure that all notifiers will be unregistered and associated 261562306a36Sopenharmony_ci * entries/resources freed when the controller is being shut down. 261662306a36Sopenharmony_ci */ 261762306a36Sopenharmony_cistatic void ssam_notifier_unregister_all(struct ssam_controller *ctrl) 261862306a36Sopenharmony_ci{ 261962306a36Sopenharmony_ci struct ssam_nf *nf = &ctrl->cplt.event.notif; 262062306a36Sopenharmony_ci struct ssam_nf_refcount_entry *e, *n; 262162306a36Sopenharmony_ci 262262306a36Sopenharmony_ci mutex_lock(&nf->lock); 262362306a36Sopenharmony_ci rbtree_postorder_for_each_entry_safe(e, n, &nf->refcount, node) { 262462306a36Sopenharmony_ci /* Ignore errors, will get logged in call. */ 262562306a36Sopenharmony_ci ssam_ssh_event_disable(ctrl, e->key.reg, e->key.id, e->flags); 262662306a36Sopenharmony_ci kfree(e); 262762306a36Sopenharmony_ci } 262862306a36Sopenharmony_ci nf->refcount = RB_ROOT; 262962306a36Sopenharmony_ci mutex_unlock(&nf->lock); 263062306a36Sopenharmony_ci} 263162306a36Sopenharmony_ci 263262306a36Sopenharmony_ci 263362306a36Sopenharmony_ci/* -- Wakeup IRQ. ----------------------------------------------------------- */ 263462306a36Sopenharmony_ci 263562306a36Sopenharmony_cistatic irqreturn_t ssam_irq_handle(int irq, void *dev_id) 263662306a36Sopenharmony_ci{ 263762306a36Sopenharmony_ci struct ssam_controller *ctrl = dev_id; 263862306a36Sopenharmony_ci 263962306a36Sopenharmony_ci ssam_dbg(ctrl, "pm: wake irq triggered\n"); 264062306a36Sopenharmony_ci 264162306a36Sopenharmony_ci /* 264262306a36Sopenharmony_ci * Note: Proper wakeup detection is currently unimplemented. 264362306a36Sopenharmony_ci * When the EC is in display-off or any other non-D0 state, it 264462306a36Sopenharmony_ci * does not send events/notifications to the host. Instead it 264562306a36Sopenharmony_ci * signals that there are events available via the wakeup IRQ. 264662306a36Sopenharmony_ci * This driver is responsible for calling back to the EC to 264762306a36Sopenharmony_ci * release these events one-by-one. 264862306a36Sopenharmony_ci * 264962306a36Sopenharmony_ci * This IRQ should not cause a full system resume by its own. 265062306a36Sopenharmony_ci * Instead, events should be handled by their respective subsystem 265162306a36Sopenharmony_ci * drivers, which in turn should signal whether a full system 265262306a36Sopenharmony_ci * resume should be performed. 265362306a36Sopenharmony_ci * 265462306a36Sopenharmony_ci * TODO: Send GPIO callback command repeatedly to EC until callback 265562306a36Sopenharmony_ci * returns 0x00. Return flag of callback is "has more events". 265662306a36Sopenharmony_ci * Each time the command is sent, one event is "released". Once 265762306a36Sopenharmony_ci * all events have been released (return = 0x00), the GPIO is 265862306a36Sopenharmony_ci * re-armed. Detect wakeup events during this process, go back to 265962306a36Sopenharmony_ci * sleep if no wakeup event has been received. 266062306a36Sopenharmony_ci */ 266162306a36Sopenharmony_ci 266262306a36Sopenharmony_ci return IRQ_HANDLED; 266362306a36Sopenharmony_ci} 266462306a36Sopenharmony_ci 266562306a36Sopenharmony_ci/** 266662306a36Sopenharmony_ci * ssam_irq_setup() - Set up SAM EC wakeup-GPIO interrupt. 266762306a36Sopenharmony_ci * @ctrl: The controller for which the IRQ should be set up. 266862306a36Sopenharmony_ci * 266962306a36Sopenharmony_ci * Set up an IRQ for the wakeup-GPIO pin of the SAM EC. This IRQ can be used 267062306a36Sopenharmony_ci * to wake the device from a low power state. 267162306a36Sopenharmony_ci * 267262306a36Sopenharmony_ci * Note that this IRQ can only be triggered while the EC is in the display-off 267362306a36Sopenharmony_ci * state. In this state, events are not sent to the host in the usual way. 267462306a36Sopenharmony_ci * Instead the wakeup-GPIO gets pulled to "high" as long as there are pending 267562306a36Sopenharmony_ci * events and these events need to be released one-by-one via the GPIO 267662306a36Sopenharmony_ci * callback request, either until there are no events left and the GPIO is 267762306a36Sopenharmony_ci * reset, or all at once by transitioning the EC out of the display-off state, 267862306a36Sopenharmony_ci * which will also clear the GPIO. 267962306a36Sopenharmony_ci * 268062306a36Sopenharmony_ci * Not all events, however, should trigger a full system wakeup. Instead the 268162306a36Sopenharmony_ci * driver should, if necessary, inspect and forward each event to the 268262306a36Sopenharmony_ci * corresponding subsystem, which in turn should decide if the system needs to 268362306a36Sopenharmony_ci * be woken up. This logic has not been implemented yet, thus wakeup by this 268462306a36Sopenharmony_ci * IRQ should be disabled by default to avoid spurious wake-ups, caused, for 268562306a36Sopenharmony_ci * example, by the remaining battery percentage changing. Refer to comments in 268662306a36Sopenharmony_ci * this function and comments in the corresponding IRQ handler for more 268762306a36Sopenharmony_ci * details on how this should be implemented. 268862306a36Sopenharmony_ci * 268962306a36Sopenharmony_ci * See also ssam_ctrl_notif_display_off() and ssam_ctrl_notif_display_off() 269062306a36Sopenharmony_ci * for functions to transition the EC into and out of the display-off state as 269162306a36Sopenharmony_ci * well as more details on it. 269262306a36Sopenharmony_ci * 269362306a36Sopenharmony_ci * The IRQ is disabled by default and has to be enabled before it can wake up 269462306a36Sopenharmony_ci * the device from suspend via ssam_irq_arm_for_wakeup(). On teardown, the IRQ 269562306a36Sopenharmony_ci * should be freed via ssam_irq_free(). 269662306a36Sopenharmony_ci */ 269762306a36Sopenharmony_ciint ssam_irq_setup(struct ssam_controller *ctrl) 269862306a36Sopenharmony_ci{ 269962306a36Sopenharmony_ci struct device *dev = ssam_controller_device(ctrl); 270062306a36Sopenharmony_ci struct gpio_desc *gpiod; 270162306a36Sopenharmony_ci int irq; 270262306a36Sopenharmony_ci int status; 270362306a36Sopenharmony_ci 270462306a36Sopenharmony_ci /* 270562306a36Sopenharmony_ci * The actual GPIO interrupt is declared in ACPI as TRIGGER_HIGH. 270662306a36Sopenharmony_ci * However, the GPIO line only gets reset by sending the GPIO callback 270762306a36Sopenharmony_ci * command to SAM (or alternatively the display-on notification). As 270862306a36Sopenharmony_ci * proper handling for this interrupt is not implemented yet, leaving 270962306a36Sopenharmony_ci * the IRQ at TRIGGER_HIGH would cause an IRQ storm (as the callback 271062306a36Sopenharmony_ci * never gets sent and thus the line never gets reset). To avoid this, 271162306a36Sopenharmony_ci * mark the IRQ as TRIGGER_RISING for now, only creating a single 271262306a36Sopenharmony_ci * interrupt, and let the SAM resume callback during the controller 271362306a36Sopenharmony_ci * resume process clear it. 271462306a36Sopenharmony_ci */ 271562306a36Sopenharmony_ci const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; 271662306a36Sopenharmony_ci 271762306a36Sopenharmony_ci gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); 271862306a36Sopenharmony_ci if (IS_ERR(gpiod)) 271962306a36Sopenharmony_ci return PTR_ERR(gpiod); 272062306a36Sopenharmony_ci 272162306a36Sopenharmony_ci irq = gpiod_to_irq(gpiod); 272262306a36Sopenharmony_ci gpiod_put(gpiod); 272362306a36Sopenharmony_ci 272462306a36Sopenharmony_ci if (irq < 0) 272562306a36Sopenharmony_ci return irq; 272662306a36Sopenharmony_ci 272762306a36Sopenharmony_ci status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf, 272862306a36Sopenharmony_ci "ssam_wakeup", ctrl); 272962306a36Sopenharmony_ci if (status) 273062306a36Sopenharmony_ci return status; 273162306a36Sopenharmony_ci 273262306a36Sopenharmony_ci ctrl->irq.num = irq; 273362306a36Sopenharmony_ci return 0; 273462306a36Sopenharmony_ci} 273562306a36Sopenharmony_ci 273662306a36Sopenharmony_ci/** 273762306a36Sopenharmony_ci * ssam_irq_free() - Free SAM EC wakeup-GPIO interrupt. 273862306a36Sopenharmony_ci * @ctrl: The controller for which the IRQ should be freed. 273962306a36Sopenharmony_ci * 274062306a36Sopenharmony_ci * Free the wakeup-GPIO IRQ previously set-up via ssam_irq_setup(). 274162306a36Sopenharmony_ci */ 274262306a36Sopenharmony_civoid ssam_irq_free(struct ssam_controller *ctrl) 274362306a36Sopenharmony_ci{ 274462306a36Sopenharmony_ci free_irq(ctrl->irq.num, ctrl); 274562306a36Sopenharmony_ci ctrl->irq.num = -1; 274662306a36Sopenharmony_ci} 274762306a36Sopenharmony_ci 274862306a36Sopenharmony_ci/** 274962306a36Sopenharmony_ci * ssam_irq_arm_for_wakeup() - Arm the EC IRQ for wakeup, if enabled. 275062306a36Sopenharmony_ci * @ctrl: The controller for which the IRQ should be armed. 275162306a36Sopenharmony_ci * 275262306a36Sopenharmony_ci * Sets up the IRQ so that it can be used to wake the device. Specifically, 275362306a36Sopenharmony_ci * this function enables the irq and then, if the device is allowed to wake up 275462306a36Sopenharmony_ci * the system, calls enable_irq_wake(). See ssam_irq_disarm_wakeup() for the 275562306a36Sopenharmony_ci * corresponding function to disable the IRQ. 275662306a36Sopenharmony_ci * 275762306a36Sopenharmony_ci * This function is intended to arm the IRQ before entering S2idle suspend. 275862306a36Sopenharmony_ci * 275962306a36Sopenharmony_ci * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must 276062306a36Sopenharmony_ci * be balanced. 276162306a36Sopenharmony_ci */ 276262306a36Sopenharmony_ciint ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl) 276362306a36Sopenharmony_ci{ 276462306a36Sopenharmony_ci struct device *dev = ssam_controller_device(ctrl); 276562306a36Sopenharmony_ci int status; 276662306a36Sopenharmony_ci 276762306a36Sopenharmony_ci enable_irq(ctrl->irq.num); 276862306a36Sopenharmony_ci if (device_may_wakeup(dev)) { 276962306a36Sopenharmony_ci status = enable_irq_wake(ctrl->irq.num); 277062306a36Sopenharmony_ci if (status) { 277162306a36Sopenharmony_ci ssam_err(ctrl, "failed to enable wake IRQ: %d\n", status); 277262306a36Sopenharmony_ci disable_irq(ctrl->irq.num); 277362306a36Sopenharmony_ci return status; 277462306a36Sopenharmony_ci } 277562306a36Sopenharmony_ci 277662306a36Sopenharmony_ci ctrl->irq.wakeup_enabled = true; 277762306a36Sopenharmony_ci } else { 277862306a36Sopenharmony_ci ctrl->irq.wakeup_enabled = false; 277962306a36Sopenharmony_ci } 278062306a36Sopenharmony_ci 278162306a36Sopenharmony_ci return 0; 278262306a36Sopenharmony_ci} 278362306a36Sopenharmony_ci 278462306a36Sopenharmony_ci/** 278562306a36Sopenharmony_ci * ssam_irq_disarm_wakeup() - Disarm the wakeup IRQ. 278662306a36Sopenharmony_ci * @ctrl: The controller for which the IRQ should be disarmed. 278762306a36Sopenharmony_ci * 278862306a36Sopenharmony_ci * Disarm the IRQ previously set up for wake via ssam_irq_arm_for_wakeup(). 278962306a36Sopenharmony_ci * 279062306a36Sopenharmony_ci * This function is intended to disarm the IRQ after exiting S2idle suspend. 279162306a36Sopenharmony_ci * 279262306a36Sopenharmony_ci * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must 279362306a36Sopenharmony_ci * be balanced. 279462306a36Sopenharmony_ci */ 279562306a36Sopenharmony_civoid ssam_irq_disarm_wakeup(struct ssam_controller *ctrl) 279662306a36Sopenharmony_ci{ 279762306a36Sopenharmony_ci int status; 279862306a36Sopenharmony_ci 279962306a36Sopenharmony_ci if (ctrl->irq.wakeup_enabled) { 280062306a36Sopenharmony_ci status = disable_irq_wake(ctrl->irq.num); 280162306a36Sopenharmony_ci if (status) 280262306a36Sopenharmony_ci ssam_err(ctrl, "failed to disable wake IRQ: %d\n", status); 280362306a36Sopenharmony_ci 280462306a36Sopenharmony_ci ctrl->irq.wakeup_enabled = false; 280562306a36Sopenharmony_ci } 280662306a36Sopenharmony_ci disable_irq(ctrl->irq.num); 280762306a36Sopenharmony_ci} 2808