1c167b9c7SMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
2c167b9c7SMaximilian Luz /*
3c167b9c7SMaximilian Luz  * Main SSAM/SSH controller structure and functionality.
4c167b9c7SMaximilian Luz  *
5221756e6SMaximilian Luz  * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
6c167b9c7SMaximilian Luz  */
7c167b9c7SMaximilian Luz 
8c167b9c7SMaximilian Luz #include <linux/acpi.h>
9c167b9c7SMaximilian Luz #include <linux/atomic.h>
10c167b9c7SMaximilian Luz #include <linux/completion.h>
11c167b9c7SMaximilian Luz #include <linux/gpio/consumer.h>
12c167b9c7SMaximilian Luz #include <linux/interrupt.h>
13c167b9c7SMaximilian Luz #include <linux/kref.h>
14c167b9c7SMaximilian Luz #include <linux/limits.h>
15c167b9c7SMaximilian Luz #include <linux/list.h>
16c167b9c7SMaximilian Luz #include <linux/lockdep.h>
17c167b9c7SMaximilian Luz #include <linux/mutex.h>
18c167b9c7SMaximilian Luz #include <linux/rculist.h>
19c167b9c7SMaximilian Luz #include <linux/rbtree.h>
20c167b9c7SMaximilian Luz #include <linux/rwsem.h>
21c167b9c7SMaximilian Luz #include <linux/serdev.h>
22c167b9c7SMaximilian Luz #include <linux/slab.h>
23c167b9c7SMaximilian Luz #include <linux/spinlock.h>
24c167b9c7SMaximilian Luz #include <linux/srcu.h>
25c167b9c7SMaximilian Luz #include <linux/types.h>
26c167b9c7SMaximilian Luz #include <linux/workqueue.h>
27c167b9c7SMaximilian Luz 
28c167b9c7SMaximilian Luz #include <linux/surface_aggregator/controller.h>
29c167b9c7SMaximilian Luz #include <linux/surface_aggregator/serial_hub.h>
30c167b9c7SMaximilian Luz 
31c167b9c7SMaximilian Luz #include "controller.h"
32c167b9c7SMaximilian Luz #include "ssh_msgb.h"
33c167b9c7SMaximilian Luz #include "ssh_request_layer.h"
34c167b9c7SMaximilian Luz 
350d21bb85SMaximilian Luz #include "trace.h"
360d21bb85SMaximilian Luz 
37c167b9c7SMaximilian Luz 
38c167b9c7SMaximilian Luz /* -- Safe counters. -------------------------------------------------------- */
39c167b9c7SMaximilian Luz 
40c167b9c7SMaximilian Luz /**
41c167b9c7SMaximilian Luz  * ssh_seq_reset() - Reset/initialize sequence ID counter.
42c167b9c7SMaximilian Luz  * @c: The counter to reset.
43c167b9c7SMaximilian Luz  */
ssh_seq_reset(struct ssh_seq_counter * c)44c167b9c7SMaximilian Luz static void ssh_seq_reset(struct ssh_seq_counter *c)
45c167b9c7SMaximilian Luz {
46c167b9c7SMaximilian Luz 	WRITE_ONCE(c->value, 0);
47c167b9c7SMaximilian Luz }
48c167b9c7SMaximilian Luz 
49c167b9c7SMaximilian Luz /**
50c167b9c7SMaximilian Luz  * ssh_seq_next() - Get next sequence ID.
51c167b9c7SMaximilian Luz  * @c: The counter providing the sequence IDs.
52c167b9c7SMaximilian Luz  *
53c167b9c7SMaximilian Luz  * Return: Returns the next sequence ID of the counter.
54c167b9c7SMaximilian Luz  */
ssh_seq_next(struct ssh_seq_counter * c)55c167b9c7SMaximilian Luz static u8 ssh_seq_next(struct ssh_seq_counter *c)
56c167b9c7SMaximilian Luz {
57c167b9c7SMaximilian Luz 	u8 old = READ_ONCE(c->value);
58c167b9c7SMaximilian Luz 	u8 new = old + 1;
59c167b9c7SMaximilian Luz 	u8 ret;
60c167b9c7SMaximilian Luz 
61c167b9c7SMaximilian Luz 	while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) {
62c167b9c7SMaximilian Luz 		old = ret;
63c167b9c7SMaximilian Luz 		new = old + 1;
64c167b9c7SMaximilian Luz 	}
65c167b9c7SMaximilian Luz 
66c167b9c7SMaximilian Luz 	return old;
67c167b9c7SMaximilian Luz }
68c167b9c7SMaximilian Luz 
69c167b9c7SMaximilian Luz /**
70c167b9c7SMaximilian Luz  * ssh_rqid_reset() - Reset/initialize request ID counter.
71c167b9c7SMaximilian Luz  * @c: The counter to reset.
72c167b9c7SMaximilian Luz  */
ssh_rqid_reset(struct ssh_rqid_counter * c)73c167b9c7SMaximilian Luz static void ssh_rqid_reset(struct ssh_rqid_counter *c)
74c167b9c7SMaximilian Luz {
75c167b9c7SMaximilian Luz 	WRITE_ONCE(c->value, 0);
76c167b9c7SMaximilian Luz }
77c167b9c7SMaximilian Luz 
78c167b9c7SMaximilian Luz /**
79c167b9c7SMaximilian Luz  * ssh_rqid_next() - Get next request ID.
80c167b9c7SMaximilian Luz  * @c: The counter providing the request IDs.
81c167b9c7SMaximilian Luz  *
82c167b9c7SMaximilian Luz  * Return: Returns the next request ID of the counter, skipping any reserved
83c167b9c7SMaximilian Luz  * request IDs.
84c167b9c7SMaximilian Luz  */
ssh_rqid_next(struct ssh_rqid_counter * c)85c167b9c7SMaximilian Luz static u16 ssh_rqid_next(struct ssh_rqid_counter *c)
86c167b9c7SMaximilian Luz {
87c167b9c7SMaximilian Luz 	u16 old = READ_ONCE(c->value);
88c167b9c7SMaximilian Luz 	u16 new = ssh_rqid_next_valid(old);
89c167b9c7SMaximilian Luz 	u16 ret;
90c167b9c7SMaximilian Luz 
91c167b9c7SMaximilian Luz 	while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) {
92c167b9c7SMaximilian Luz 		old = ret;
93c167b9c7SMaximilian Luz 		new = ssh_rqid_next_valid(old);
94c167b9c7SMaximilian Luz 	}
95c167b9c7SMaximilian Luz 
96c167b9c7SMaximilian Luz 	return old;
97c167b9c7SMaximilian Luz }
98c167b9c7SMaximilian Luz 
99c167b9c7SMaximilian Luz 
100c167b9c7SMaximilian Luz /* -- Event notifier/callbacks. --------------------------------------------- */
101c167b9c7SMaximilian Luz /*
102c167b9c7SMaximilian Luz  * The notifier system is based on linux/notifier.h, specifically the SRCU
103c167b9c7SMaximilian Luz  * implementation. The difference to that is, that some bits of the notifier
104c167b9c7SMaximilian Luz  * call return value can be tracked across multiple calls. This is done so
105c167b9c7SMaximilian Luz  * that handling of events can be tracked and a warning can be issued in case
106c167b9c7SMaximilian Luz  * an event goes unhandled. The idea of that warning is that it should help
107c167b9c7SMaximilian Luz  * discover and identify new/currently unimplemented features.
108c167b9c7SMaximilian Luz  */
109c167b9c7SMaximilian Luz 
110c167b9c7SMaximilian Luz /**
111c167b9c7SMaximilian Luz  * ssam_event_matches_notifier() - Test if an event matches a notifier.
112c167b9c7SMaximilian Luz  * @n: The event notifier to test against.
113c167b9c7SMaximilian Luz  * @event: The event to test.
114c167b9c7SMaximilian Luz  *
115c167b9c7SMaximilian Luz  * Return: Returns %true if the given event matches the given notifier
116c167b9c7SMaximilian Luz  * according to the rules set in the notifier's event mask, %false otherwise.
117c167b9c7SMaximilian Luz  */
ssam_event_matches_notifier(const struct ssam_event_notifier * n,const struct ssam_event * event)118c167b9c7SMaximilian Luz static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n,
119c167b9c7SMaximilian Luz 					const struct ssam_event *event)
120c167b9c7SMaximilian Luz {
121c167b9c7SMaximilian Luz 	bool match = n->event.id.target_category == event->target_category;
122c167b9c7SMaximilian Luz 
123c167b9c7SMaximilian Luz 	if (n->event.mask & SSAM_EVENT_MASK_TARGET)
124c167b9c7SMaximilian Luz 		match &= n->event.reg.target_id == event->target_id;
125c167b9c7SMaximilian Luz 
126c167b9c7SMaximilian Luz 	if (n->event.mask & SSAM_EVENT_MASK_INSTANCE)
127c167b9c7SMaximilian Luz 		match &= n->event.id.instance == event->instance_id;
128c167b9c7SMaximilian Luz 
129c167b9c7SMaximilian Luz 	return match;
130c167b9c7SMaximilian Luz }
131c167b9c7SMaximilian Luz 
132c167b9c7SMaximilian Luz /**
133c167b9c7SMaximilian Luz  * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain.
134c167b9c7SMaximilian Luz  * @nh:    The notifier head for which the notifier callbacks should be called.
135c167b9c7SMaximilian Luz  * @event: The event data provided to the callbacks.
136c167b9c7SMaximilian Luz  *
137c167b9c7SMaximilian Luz  * Call all registered notifier callbacks in order of their priority until
138c167b9c7SMaximilian Luz  * either no notifier is left or a notifier returns a value with the
139c167b9c7SMaximilian Luz  * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via
140c167b9c7SMaximilian Luz  * ssam_notifier_from_errno() on any non-zero error value.
141c167b9c7SMaximilian Luz  *
142c167b9c7SMaximilian Luz  * Return: Returns the notifier status value, which contains the notifier
143c167b9c7SMaximilian Luz  * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a
144c167b9c7SMaximilian Luz  * potential error value returned from the last executed notifier callback.
145c167b9c7SMaximilian Luz  * Use ssam_notifier_to_errno() to convert this value to the original error
146c167b9c7SMaximilian Luz  * value.
147c167b9c7SMaximilian Luz  */
ssam_nfblk_call_chain(struct ssam_nf_head * nh,struct ssam_event * event)148c167b9c7SMaximilian Luz static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event)
149c167b9c7SMaximilian Luz {
150c167b9c7SMaximilian Luz 	struct ssam_event_notifier *nf;
151c167b9c7SMaximilian Luz 	int ret = 0, idx;
152c167b9c7SMaximilian Luz 
153c167b9c7SMaximilian Luz 	idx = srcu_read_lock(&nh->srcu);
154c167b9c7SMaximilian Luz 
155c167b9c7SMaximilian Luz 	list_for_each_entry_rcu(nf, &nh->head, base.node,
156c167b9c7SMaximilian Luz 				srcu_read_lock_held(&nh->srcu)) {
157c167b9c7SMaximilian Luz 		if (ssam_event_matches_notifier(nf, event)) {
158c167b9c7SMaximilian Luz 			ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event);
159c167b9c7SMaximilian Luz 			if (ret & SSAM_NOTIF_STOP)
160c167b9c7SMaximilian Luz 				break;
161c167b9c7SMaximilian Luz 		}
162c167b9c7SMaximilian Luz 	}
163c167b9c7SMaximilian Luz 
164c167b9c7SMaximilian Luz 	srcu_read_unlock(&nh->srcu, idx);
165c167b9c7SMaximilian Luz 	return ret;
166c167b9c7SMaximilian Luz }
167c167b9c7SMaximilian Luz 
168c167b9c7SMaximilian Luz /**
169c167b9c7SMaximilian Luz  * ssam_nfblk_insert() - Insert a new notifier block into the given notifier
170c167b9c7SMaximilian Luz  * list.
171c167b9c7SMaximilian Luz  * @nh: The notifier head into which the block should be inserted.
172c167b9c7SMaximilian Luz  * @nb: The notifier block to add.
173c167b9c7SMaximilian Luz  *
174c167b9c7SMaximilian Luz  * Note: This function must be synchronized by the caller with respect to other
175c167b9c7SMaximilian Luz  * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``.
176c167b9c7SMaximilian Luz  *
177c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-EEXIST if the notifier block has already
178c167b9c7SMaximilian Luz  * been registered.
179c167b9c7SMaximilian Luz  */
ssam_nfblk_insert(struct ssam_nf_head * nh,struct ssam_notifier_block * nb)180c167b9c7SMaximilian Luz static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb)
181c167b9c7SMaximilian Luz {
182c167b9c7SMaximilian Luz 	struct ssam_notifier_block *p;
183c167b9c7SMaximilian Luz 	struct list_head *h;
184c167b9c7SMaximilian Luz 
185c167b9c7SMaximilian Luz 	/* Runs under lock, no need for RCU variant. */
186c167b9c7SMaximilian Luz 	list_for_each(h, &nh->head) {
187c167b9c7SMaximilian Luz 		p = list_entry(h, struct ssam_notifier_block, node);
188c167b9c7SMaximilian Luz 
189c167b9c7SMaximilian Luz 		if (unlikely(p == nb)) {
190c167b9c7SMaximilian Luz 			WARN(1, "double register detected");
191c167b9c7SMaximilian Luz 			return -EEXIST;
192c167b9c7SMaximilian Luz 		}
193c167b9c7SMaximilian Luz 
194c167b9c7SMaximilian Luz 		if (nb->priority > p->priority)
195c167b9c7SMaximilian Luz 			break;
196c167b9c7SMaximilian Luz 	}
197c167b9c7SMaximilian Luz 
198c167b9c7SMaximilian Luz 	list_add_tail_rcu(&nb->node, h);
199c167b9c7SMaximilian Luz 	return 0;
200c167b9c7SMaximilian Luz }
201c167b9c7SMaximilian Luz 
202c167b9c7SMaximilian Luz /**
203c167b9c7SMaximilian Luz  * ssam_nfblk_find() - Check if a notifier block is registered on the given
204c167b9c7SMaximilian Luz  * notifier head.
205c167b9c7SMaximilian Luz  * list.
206c167b9c7SMaximilian Luz  * @nh: The notifier head on which to search.
207c167b9c7SMaximilian Luz  * @nb: The notifier block to search for.
208c167b9c7SMaximilian Luz  *
209c167b9c7SMaximilian Luz  * Note: This function must be synchronized by the caller with respect to other
210c167b9c7SMaximilian Luz  * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``.
211c167b9c7SMaximilian Luz  *
212c167b9c7SMaximilian Luz  * Return: Returns true if the given notifier block is registered on the given
213c167b9c7SMaximilian Luz  * notifier head, false otherwise.
214c167b9c7SMaximilian Luz  */
ssam_nfblk_find(struct ssam_nf_head * nh,struct ssam_notifier_block * nb)215c167b9c7SMaximilian Luz static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb)
216c167b9c7SMaximilian Luz {
217c167b9c7SMaximilian Luz 	struct ssam_notifier_block *p;
218c167b9c7SMaximilian Luz 
219c167b9c7SMaximilian Luz 	/* Runs under lock, no need for RCU variant. */
220c167b9c7SMaximilian Luz 	list_for_each_entry(p, &nh->head, node) {
221c167b9c7SMaximilian Luz 		if (p == nb)
222c167b9c7SMaximilian Luz 			return true;
223c167b9c7SMaximilian Luz 	}
224c167b9c7SMaximilian Luz 
225c167b9c7SMaximilian Luz 	return false;
226c167b9c7SMaximilian Luz }
227c167b9c7SMaximilian Luz 
228c167b9c7SMaximilian Luz /**
229c167b9c7SMaximilian Luz  * ssam_nfblk_remove() - Remove a notifier block from its notifier list.
230c167b9c7SMaximilian Luz  * @nb: The notifier block to be removed.
231c167b9c7SMaximilian Luz  *
232c167b9c7SMaximilian Luz  * Note: This function must be synchronized by the caller with respect to
233c167b9c7SMaximilian Luz  * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``.
234c167b9c7SMaximilian Luz  * Furthermore, the caller _must_ ensure SRCU synchronization by calling
235c167b9c7SMaximilian Luz  * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to
236c167b9c7SMaximilian Luz  * ensure that the removed notifier block is not in use any more.
237c167b9c7SMaximilian Luz  */
ssam_nfblk_remove(struct ssam_notifier_block * nb)238c167b9c7SMaximilian Luz static void ssam_nfblk_remove(struct ssam_notifier_block *nb)
239c167b9c7SMaximilian Luz {
240c167b9c7SMaximilian Luz 	list_del_rcu(&nb->node);
241c167b9c7SMaximilian Luz }
242c167b9c7SMaximilian Luz 
243c167b9c7SMaximilian Luz /**
244c167b9c7SMaximilian Luz  * ssam_nf_head_init() - Initialize the given notifier head.
245c167b9c7SMaximilian Luz  * @nh: The notifier head to initialize.
246c167b9c7SMaximilian Luz  */
ssam_nf_head_init(struct ssam_nf_head * nh)247c167b9c7SMaximilian Luz static int ssam_nf_head_init(struct ssam_nf_head *nh)
248c167b9c7SMaximilian Luz {
249c167b9c7SMaximilian Luz 	int status;
250c167b9c7SMaximilian Luz 
251c167b9c7SMaximilian Luz 	status = init_srcu_struct(&nh->srcu);
252c167b9c7SMaximilian Luz 	if (status)
253c167b9c7SMaximilian Luz 		return status;
254c167b9c7SMaximilian Luz 
255c167b9c7SMaximilian Luz 	INIT_LIST_HEAD(&nh->head);
256c167b9c7SMaximilian Luz 	return 0;
257c167b9c7SMaximilian Luz }
258c167b9c7SMaximilian Luz 
259c167b9c7SMaximilian Luz /**
260c167b9c7SMaximilian Luz  * ssam_nf_head_destroy() - Deinitialize the given notifier head.
261c167b9c7SMaximilian Luz  * @nh: The notifier head to deinitialize.
262c167b9c7SMaximilian Luz  */
ssam_nf_head_destroy(struct ssam_nf_head * nh)263c167b9c7SMaximilian Luz static void ssam_nf_head_destroy(struct ssam_nf_head *nh)
264c167b9c7SMaximilian Luz {
265c167b9c7SMaximilian Luz 	cleanup_srcu_struct(&nh->srcu);
266c167b9c7SMaximilian Luz }
267c167b9c7SMaximilian Luz 
268c167b9c7SMaximilian Luz 
269c167b9c7SMaximilian Luz /* -- Event/notification registry. ------------------------------------------ */
270c167b9c7SMaximilian Luz 
271c167b9c7SMaximilian Luz /**
272c167b9c7SMaximilian Luz  * struct ssam_nf_refcount_key - Key used for event activation reference
273c167b9c7SMaximilian Luz  * counting.
274c167b9c7SMaximilian Luz  * @reg: The registry via which the event is enabled/disabled.
275c167b9c7SMaximilian Luz  * @id:  The ID uniquely describing the event.
276c167b9c7SMaximilian Luz  */
277c167b9c7SMaximilian Luz struct ssam_nf_refcount_key {
278c167b9c7SMaximilian Luz 	struct ssam_event_registry reg;
279c167b9c7SMaximilian Luz 	struct ssam_event_id id;
280c167b9c7SMaximilian Luz };
281c167b9c7SMaximilian Luz 
282c167b9c7SMaximilian Luz /**
283c167b9c7SMaximilian Luz  * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event
284c167b9c7SMaximilian Luz  * activations.
285c167b9c7SMaximilian Luz  * @node:     The node of this entry in the rb-tree.
286c167b9c7SMaximilian Luz  * @key:      The key of the event.
287c167b9c7SMaximilian Luz  * @refcount: The reference-count of the event.
288c167b9c7SMaximilian Luz  * @flags:    The flags used when enabling the event.
289c167b9c7SMaximilian Luz  */
290c167b9c7SMaximilian Luz struct ssam_nf_refcount_entry {
291c167b9c7SMaximilian Luz 	struct rb_node node;
292c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_key key;
293c167b9c7SMaximilian Luz 	int refcount;
294c167b9c7SMaximilian Luz 	u8 flags;
295c167b9c7SMaximilian Luz };
296c167b9c7SMaximilian Luz 
297c167b9c7SMaximilian Luz /**
298c167b9c7SMaximilian Luz  * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given
299c167b9c7SMaximilian Luz  * event.
300c167b9c7SMaximilian Luz  * @nf:  The notifier system reference.
301c167b9c7SMaximilian Luz  * @reg: The registry used to enable/disable the event.
302c167b9c7SMaximilian Luz  * @id:  The event ID.
303c167b9c7SMaximilian Luz  *
304c167b9c7SMaximilian Luz  * Increments the reference-/activation-count associated with the specified
305c167b9c7SMaximilian Luz  * event type/ID, allocating a new entry for this event ID if necessary. A
306c167b9c7SMaximilian Luz  * newly allocated entry will have a refcount of one.
307c167b9c7SMaximilian Luz  *
308c167b9c7SMaximilian Luz  * Note: ``nf->lock`` must be held when calling this function.
309c167b9c7SMaximilian Luz  *
310c167b9c7SMaximilian Luz  * Return: Returns the refcount entry on success. Returns an error pointer
311c167b9c7SMaximilian Luz  * with %-ENOSPC if there have already been %INT_MAX events of the specified
312c167b9c7SMaximilian Luz  * ID and type registered, or %-ENOMEM if the entry could not be allocated.
313c167b9c7SMaximilian Luz  */
314c167b9c7SMaximilian Luz static struct ssam_nf_refcount_entry *
ssam_nf_refcount_inc(struct ssam_nf * nf,struct ssam_event_registry reg,struct ssam_event_id id)315c167b9c7SMaximilian Luz ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg,
316c167b9c7SMaximilian Luz 		     struct ssam_event_id id)
317c167b9c7SMaximilian Luz {
318c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
319c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_key key;
320c167b9c7SMaximilian Luz 	struct rb_node **link = &nf->refcount.rb_node;
321c167b9c7SMaximilian Luz 	struct rb_node *parent = NULL;
322c167b9c7SMaximilian Luz 	int cmp;
323c167b9c7SMaximilian Luz 
324c167b9c7SMaximilian Luz 	lockdep_assert_held(&nf->lock);
325c167b9c7SMaximilian Luz 
326c167b9c7SMaximilian Luz 	key.reg = reg;
327c167b9c7SMaximilian Luz 	key.id = id;
328c167b9c7SMaximilian Luz 
329c167b9c7SMaximilian Luz 	while (*link) {
330c167b9c7SMaximilian Luz 		entry = rb_entry(*link, struct ssam_nf_refcount_entry, node);
331c167b9c7SMaximilian Luz 		parent = *link;
332c167b9c7SMaximilian Luz 
333c167b9c7SMaximilian Luz 		cmp = memcmp(&key, &entry->key, sizeof(key));
334c167b9c7SMaximilian Luz 		if (cmp < 0) {
335c167b9c7SMaximilian Luz 			link = &(*link)->rb_left;
336c167b9c7SMaximilian Luz 		} else if (cmp > 0) {
337c167b9c7SMaximilian Luz 			link = &(*link)->rb_right;
338c167b9c7SMaximilian Luz 		} else if (entry->refcount < INT_MAX) {
339c167b9c7SMaximilian Luz 			entry->refcount++;
340c167b9c7SMaximilian Luz 			return entry;
341c167b9c7SMaximilian Luz 		} else {
342c167b9c7SMaximilian Luz 			WARN_ON(1);
343c167b9c7SMaximilian Luz 			return ERR_PTR(-ENOSPC);
344c167b9c7SMaximilian Luz 		}
345c167b9c7SMaximilian Luz 	}
346c167b9c7SMaximilian Luz 
347c167b9c7SMaximilian Luz 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
348c167b9c7SMaximilian Luz 	if (!entry)
349c167b9c7SMaximilian Luz 		return ERR_PTR(-ENOMEM);
350c167b9c7SMaximilian Luz 
351c167b9c7SMaximilian Luz 	entry->key = key;
352c167b9c7SMaximilian Luz 	entry->refcount = 1;
353c167b9c7SMaximilian Luz 
354c167b9c7SMaximilian Luz 	rb_link_node(&entry->node, parent, link);
355c167b9c7SMaximilian Luz 	rb_insert_color(&entry->node, &nf->refcount);
356c167b9c7SMaximilian Luz 
357c167b9c7SMaximilian Luz 	return entry;
358c167b9c7SMaximilian Luz }
359c167b9c7SMaximilian Luz 
360c167b9c7SMaximilian Luz /**
361c167b9c7SMaximilian Luz  * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given
362c167b9c7SMaximilian Luz  * event.
363c167b9c7SMaximilian Luz  * @nf:  The notifier system reference.
364c167b9c7SMaximilian Luz  * @reg: The registry used to enable/disable the event.
365c167b9c7SMaximilian Luz  * @id:  The event ID.
366c167b9c7SMaximilian Luz  *
367c167b9c7SMaximilian Luz  * Decrements the reference-/activation-count of the specified event,
368c167b9c7SMaximilian Luz  * returning its entry. If the returned entry has a refcount of zero, the
369c167b9c7SMaximilian Luz  * caller is responsible for freeing it using kfree().
370c167b9c7SMaximilian Luz  *
371c167b9c7SMaximilian Luz  * Note: ``nf->lock`` must be held when calling this function.
372c167b9c7SMaximilian Luz  *
373c167b9c7SMaximilian Luz  * Return: Returns the refcount entry on success or %NULL if the entry has not
374c167b9c7SMaximilian Luz  * been found.
375c167b9c7SMaximilian Luz  */
376c167b9c7SMaximilian Luz static struct ssam_nf_refcount_entry *
ssam_nf_refcount_dec(struct ssam_nf * nf,struct ssam_event_registry reg,struct ssam_event_id id)377c167b9c7SMaximilian Luz ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg,
378c167b9c7SMaximilian Luz 		     struct ssam_event_id id)
379c167b9c7SMaximilian Luz {
380c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
381c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_key key;
382c167b9c7SMaximilian Luz 	struct rb_node *node = nf->refcount.rb_node;
383c167b9c7SMaximilian Luz 	int cmp;
384c167b9c7SMaximilian Luz 
385c167b9c7SMaximilian Luz 	lockdep_assert_held(&nf->lock);
386c167b9c7SMaximilian Luz 
387c167b9c7SMaximilian Luz 	key.reg = reg;
388c167b9c7SMaximilian Luz 	key.id = id;
389c167b9c7SMaximilian Luz 
390c167b9c7SMaximilian Luz 	while (node) {
391c167b9c7SMaximilian Luz 		entry = rb_entry(node, struct ssam_nf_refcount_entry, node);
392c167b9c7SMaximilian Luz 
393c167b9c7SMaximilian Luz 		cmp = memcmp(&key, &entry->key, sizeof(key));
394c167b9c7SMaximilian Luz 		if (cmp < 0) {
395c167b9c7SMaximilian Luz 			node = node->rb_left;
396c167b9c7SMaximilian Luz 		} else if (cmp > 0) {
397c167b9c7SMaximilian Luz 			node = node->rb_right;
398c167b9c7SMaximilian Luz 		} else {
399c167b9c7SMaximilian Luz 			entry->refcount--;
400c167b9c7SMaximilian Luz 			if (entry->refcount == 0)
401c167b9c7SMaximilian Luz 				rb_erase(&entry->node, &nf->refcount);
402c167b9c7SMaximilian Luz 
403c167b9c7SMaximilian Luz 			return entry;
404c167b9c7SMaximilian Luz 		}
405c167b9c7SMaximilian Luz 	}
406c167b9c7SMaximilian Luz 
407c167b9c7SMaximilian Luz 	return NULL;
408c167b9c7SMaximilian Luz }
409c167b9c7SMaximilian Luz 
410c167b9c7SMaximilian Luz /**
4114b38a1dcSMaximilian Luz  * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the
4124b38a1dcSMaximilian Luz  * given event and free its entry if the reference count reaches zero.
4134b38a1dcSMaximilian Luz  * @nf:  The notifier system reference.
4144b38a1dcSMaximilian Luz  * @reg: The registry used to enable/disable the event.
4154b38a1dcSMaximilian Luz  * @id:  The event ID.
4164b38a1dcSMaximilian Luz  *
4174b38a1dcSMaximilian Luz  * Decrements the reference-/activation-count of the specified event, freeing
4184b38a1dcSMaximilian Luz  * its entry if it reaches zero.
4194b38a1dcSMaximilian Luz  *
4204b38a1dcSMaximilian Luz  * Note: ``nf->lock`` must be held when calling this function.
4214b38a1dcSMaximilian Luz  */
ssam_nf_refcount_dec_free(struct ssam_nf * nf,struct ssam_event_registry reg,struct ssam_event_id id)4224b38a1dcSMaximilian Luz static void ssam_nf_refcount_dec_free(struct ssam_nf *nf,
4234b38a1dcSMaximilian Luz 				      struct ssam_event_registry reg,
4244b38a1dcSMaximilian Luz 				      struct ssam_event_id id)
4254b38a1dcSMaximilian Luz {
4264b38a1dcSMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
4274b38a1dcSMaximilian Luz 
4284b38a1dcSMaximilian Luz 	lockdep_assert_held(&nf->lock);
4294b38a1dcSMaximilian Luz 
4304b38a1dcSMaximilian Luz 	entry = ssam_nf_refcount_dec(nf, reg, id);
4314b38a1dcSMaximilian Luz 	if (entry && entry->refcount == 0)
4324b38a1dcSMaximilian Luz 		kfree(entry);
4334b38a1dcSMaximilian Luz }
4344b38a1dcSMaximilian Luz 
4354b38a1dcSMaximilian Luz /**
436c167b9c7SMaximilian Luz  * ssam_nf_refcount_empty() - Test if the notification system has any
437c167b9c7SMaximilian Luz  * enabled/active events.
438c167b9c7SMaximilian Luz  * @nf: The notification system.
439c167b9c7SMaximilian Luz  */
ssam_nf_refcount_empty(struct ssam_nf * nf)440c167b9c7SMaximilian Luz static bool ssam_nf_refcount_empty(struct ssam_nf *nf)
441c167b9c7SMaximilian Luz {
442c167b9c7SMaximilian Luz 	return RB_EMPTY_ROOT(&nf->refcount);
443c167b9c7SMaximilian Luz }
444c167b9c7SMaximilian Luz 
445c167b9c7SMaximilian Luz /**
446c167b9c7SMaximilian Luz  * ssam_nf_call() - Call notification callbacks for the provided event.
447c167b9c7SMaximilian Luz  * @nf:    The notifier system
448c167b9c7SMaximilian Luz  * @dev:   The associated device, only used for logging.
449c167b9c7SMaximilian Luz  * @rqid:  The request ID of the event.
450c167b9c7SMaximilian Luz  * @event: The event provided to the callbacks.
451c167b9c7SMaximilian Luz  *
452c167b9c7SMaximilian Luz  * Execute registered callbacks in order of their priority until either no
453c167b9c7SMaximilian Luz  * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP
454c167b9c7SMaximilian Luz  * bit set. Note that this bit is set automatically when converting non-zero
455c167b9c7SMaximilian Luz  * error values via ssam_notifier_from_errno() to notifier values.
456c167b9c7SMaximilian Luz  *
457c167b9c7SMaximilian Luz  * Also note that any callback that could handle an event should return a value
458c167b9c7SMaximilian Luz  * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go
459c167b9c7SMaximilian Luz  * unhandled/ignored. In case no registered callback could handle an event,
460c167b9c7SMaximilian Luz  * this function will emit a warning.
461c167b9c7SMaximilian Luz  *
462c167b9c7SMaximilian Luz  * In case a callback failed, this function will emit an error message.
463c167b9c7SMaximilian Luz  */
ssam_nf_call(struct ssam_nf * nf,struct device * dev,u16 rqid,struct ssam_event * event)464c167b9c7SMaximilian Luz static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid,
465c167b9c7SMaximilian Luz 			 struct ssam_event *event)
466c167b9c7SMaximilian Luz {
467c167b9c7SMaximilian Luz 	struct ssam_nf_head *nf_head;
468c167b9c7SMaximilian Luz 	int status, nf_ret;
469c167b9c7SMaximilian Luz 
470c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid)) {
471c167b9c7SMaximilian Luz 		dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid);
472c167b9c7SMaximilian Luz 		return;
473c167b9c7SMaximilian Luz 	}
474c167b9c7SMaximilian Luz 
475c167b9c7SMaximilian Luz 	nf_head = &nf->head[ssh_rqid_to_event(rqid)];
476c167b9c7SMaximilian Luz 	nf_ret = ssam_nfblk_call_chain(nf_head, event);
477c167b9c7SMaximilian Luz 	status = ssam_notifier_to_errno(nf_ret);
478c167b9c7SMaximilian Luz 
479c167b9c7SMaximilian Luz 	if (status < 0) {
480c167b9c7SMaximilian Luz 		dev_err(dev,
481c167b9c7SMaximilian Luz 			"event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
482c167b9c7SMaximilian Luz 			status, event->target_category, event->target_id,
483c167b9c7SMaximilian Luz 			event->command_id, event->instance_id);
484c167b9c7SMaximilian Luz 	} else if (!(nf_ret & SSAM_NOTIF_HANDLED)) {
485c167b9c7SMaximilian Luz 		dev_warn(dev,
486c167b9c7SMaximilian Luz 			 "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
487c167b9c7SMaximilian Luz 			 rqid, event->target_category, event->target_id,
488c167b9c7SMaximilian Luz 			 event->command_id, event->instance_id);
489c167b9c7SMaximilian Luz 	}
490c167b9c7SMaximilian Luz }
491c167b9c7SMaximilian Luz 
492c167b9c7SMaximilian Luz /**
493c167b9c7SMaximilian Luz  * ssam_nf_init() - Initialize the notifier system.
494c167b9c7SMaximilian Luz  * @nf: The notifier system to initialize.
495c167b9c7SMaximilian Luz  */
ssam_nf_init(struct ssam_nf * nf)496c167b9c7SMaximilian Luz static int ssam_nf_init(struct ssam_nf *nf)
497c167b9c7SMaximilian Luz {
498c167b9c7SMaximilian Luz 	int i, status;
499c167b9c7SMaximilian Luz 
500c167b9c7SMaximilian Luz 	for (i = 0; i < SSH_NUM_EVENTS; i++) {
501c167b9c7SMaximilian Luz 		status = ssam_nf_head_init(&nf->head[i]);
502c167b9c7SMaximilian Luz 		if (status)
503c167b9c7SMaximilian Luz 			break;
504c167b9c7SMaximilian Luz 	}
505c167b9c7SMaximilian Luz 
506c167b9c7SMaximilian Luz 	if (status) {
507c167b9c7SMaximilian Luz 		while (i--)
508c167b9c7SMaximilian Luz 			ssam_nf_head_destroy(&nf->head[i]);
509c167b9c7SMaximilian Luz 
510c167b9c7SMaximilian Luz 		return status;
511c167b9c7SMaximilian Luz 	}
512c167b9c7SMaximilian Luz 
513c167b9c7SMaximilian Luz 	mutex_init(&nf->lock);
514c167b9c7SMaximilian Luz 	return 0;
515c167b9c7SMaximilian Luz }
516c167b9c7SMaximilian Luz 
517c167b9c7SMaximilian Luz /**
518c167b9c7SMaximilian Luz  * ssam_nf_destroy() - Deinitialize the notifier system.
519c167b9c7SMaximilian Luz  * @nf: The notifier system to deinitialize.
520c167b9c7SMaximilian Luz  */
ssam_nf_destroy(struct ssam_nf * nf)521c167b9c7SMaximilian Luz static void ssam_nf_destroy(struct ssam_nf *nf)
522c167b9c7SMaximilian Luz {
523c167b9c7SMaximilian Luz 	int i;
524c167b9c7SMaximilian Luz 
525c167b9c7SMaximilian Luz 	for (i = 0; i < SSH_NUM_EVENTS; i++)
526c167b9c7SMaximilian Luz 		ssam_nf_head_destroy(&nf->head[i]);
527c167b9c7SMaximilian Luz 
528c167b9c7SMaximilian Luz 	mutex_destroy(&nf->lock);
529c167b9c7SMaximilian Luz }
530c167b9c7SMaximilian Luz 
531c167b9c7SMaximilian Luz 
532c167b9c7SMaximilian Luz /* -- Event/async request completion system. -------------------------------- */
533c167b9c7SMaximilian Luz 
534c167b9c7SMaximilian Luz #define SSAM_CPLT_WQ_NAME	"ssam_cpltq"
535c167b9c7SMaximilian Luz 
536c167b9c7SMaximilian Luz /*
537c167b9c7SMaximilian Luz  * SSAM_CPLT_WQ_BATCH - Maximum number of event item completions executed per
538c167b9c7SMaximilian Luz  * work execution. Used to prevent livelocking of the workqueue. Value chosen
539c167b9c7SMaximilian Luz  * via educated guess, may be adjusted.
540c167b9c7SMaximilian Luz  */
541c167b9c7SMaximilian Luz #define SSAM_CPLT_WQ_BATCH	10
542c167b9c7SMaximilian Luz 
5433a7081f6SMaximilian Luz /*
5443a7081f6SMaximilian Luz  * SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN - Maximum payload length for a cached
5453a7081f6SMaximilian Luz  * &struct ssam_event_item.
5463a7081f6SMaximilian Luz  *
5473a7081f6SMaximilian Luz  * This length has been chosen to be accommodate standard touchpad and
5483a7081f6SMaximilian Luz  * keyboard input events. Events with larger payloads will be allocated
5493a7081f6SMaximilian Luz  * separately.
5503a7081f6SMaximilian Luz  */
5513a7081f6SMaximilian Luz #define SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN	32
5523a7081f6SMaximilian Luz 
5533a7081f6SMaximilian Luz static struct kmem_cache *ssam_event_item_cache;
5543a7081f6SMaximilian Luz 
5553a7081f6SMaximilian Luz /**
5563a7081f6SMaximilian Luz  * ssam_event_item_cache_init() - Initialize the event item cache.
5573a7081f6SMaximilian Luz  */
ssam_event_item_cache_init(void)5583a7081f6SMaximilian Luz int ssam_event_item_cache_init(void)
5593a7081f6SMaximilian Luz {
5603a7081f6SMaximilian Luz 	const unsigned int size = sizeof(struct ssam_event_item)
5613a7081f6SMaximilian Luz 				  + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN;
5623a7081f6SMaximilian Luz 	const unsigned int align = __alignof__(struct ssam_event_item);
5633a7081f6SMaximilian Luz 	struct kmem_cache *cache;
5643a7081f6SMaximilian Luz 
5653a7081f6SMaximilian Luz 	cache = kmem_cache_create("ssam_event_item", size, align, 0, NULL);
5663a7081f6SMaximilian Luz 	if (!cache)
5673a7081f6SMaximilian Luz 		return -ENOMEM;
5683a7081f6SMaximilian Luz 
5693a7081f6SMaximilian Luz 	ssam_event_item_cache = cache;
5703a7081f6SMaximilian Luz 	return 0;
5713a7081f6SMaximilian Luz }
5723a7081f6SMaximilian Luz 
5733a7081f6SMaximilian Luz /**
5743a7081f6SMaximilian Luz  * ssam_event_item_cache_destroy() - Deinitialize the event item cache.
5753a7081f6SMaximilian Luz  */
ssam_event_item_cache_destroy(void)5763a7081f6SMaximilian Luz void ssam_event_item_cache_destroy(void)
5773a7081f6SMaximilian Luz {
5783a7081f6SMaximilian Luz 	kmem_cache_destroy(ssam_event_item_cache);
5793a7081f6SMaximilian Luz 	ssam_event_item_cache = NULL;
5803a7081f6SMaximilian Luz }
5813a7081f6SMaximilian Luz 
__ssam_event_item_free_cached(struct ssam_event_item * item)5823a7081f6SMaximilian Luz static void __ssam_event_item_free_cached(struct ssam_event_item *item)
5833a7081f6SMaximilian Luz {
5843a7081f6SMaximilian Luz 	kmem_cache_free(ssam_event_item_cache, item);
5853a7081f6SMaximilian Luz }
5863a7081f6SMaximilian Luz 
__ssam_event_item_free_generic(struct ssam_event_item * item)5873a7081f6SMaximilian Luz static void __ssam_event_item_free_generic(struct ssam_event_item *item)
5883a7081f6SMaximilian Luz {
5893a7081f6SMaximilian Luz 	kfree(item);
5903a7081f6SMaximilian Luz }
5913a7081f6SMaximilian Luz 
5923a7081f6SMaximilian Luz /**
5933a7081f6SMaximilian Luz  * ssam_event_item_free() - Free the provided event item.
5943a7081f6SMaximilian Luz  * @item: The event item to free.
5953a7081f6SMaximilian Luz  */
ssam_event_item_free(struct ssam_event_item * item)5963a7081f6SMaximilian Luz static void ssam_event_item_free(struct ssam_event_item *item)
5973a7081f6SMaximilian Luz {
5980d21bb85SMaximilian Luz 	trace_ssam_event_item_free(item);
5993a7081f6SMaximilian Luz 	item->ops.free(item);
6003a7081f6SMaximilian Luz }
6013a7081f6SMaximilian Luz 
602c167b9c7SMaximilian Luz /**
603c167b9c7SMaximilian Luz  * ssam_event_item_alloc() - Allocate an event item with the given payload size.
604c167b9c7SMaximilian Luz  * @len:   The event payload length.
605c167b9c7SMaximilian Luz  * @flags: The flags used for allocation.
606c167b9c7SMaximilian Luz  *
6073a7081f6SMaximilian Luz  * Allocate an event item with the given payload size, preferring allocation
6083a7081f6SMaximilian Luz  * from the event item cache if the payload is small enough (i.e. smaller than
6093a7081f6SMaximilian Luz  * %SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN). Sets the item operations and payload
6103a7081f6SMaximilian Luz  * length values. The item free callback (``ops.free``) should not be
6113a7081f6SMaximilian Luz  * overwritten after this call.
612c167b9c7SMaximilian Luz  *
613c167b9c7SMaximilian Luz  * Return: Returns the newly allocated event item.
614c167b9c7SMaximilian Luz  */
ssam_event_item_alloc(size_t len,gfp_t flags)615c167b9c7SMaximilian Luz static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags)
616c167b9c7SMaximilian Luz {
617c167b9c7SMaximilian Luz 	struct ssam_event_item *item;
618c167b9c7SMaximilian Luz 
6193a7081f6SMaximilian Luz 	if (len <= SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN) {
6203a7081f6SMaximilian Luz 		item = kmem_cache_alloc(ssam_event_item_cache, flags);
6213a7081f6SMaximilian Luz 		if (!item)
6223a7081f6SMaximilian Luz 			return NULL;
6233a7081f6SMaximilian Luz 
6243a7081f6SMaximilian Luz 		item->ops.free = __ssam_event_item_free_cached;
6253a7081f6SMaximilian Luz 	} else {
626c167b9c7SMaximilian Luz 		item = kzalloc(struct_size(item, event.data, len), flags);
627c167b9c7SMaximilian Luz 		if (!item)
628c167b9c7SMaximilian Luz 			return NULL;
629c167b9c7SMaximilian Luz 
6303a7081f6SMaximilian Luz 		item->ops.free = __ssam_event_item_free_generic;
6313a7081f6SMaximilian Luz 	}
6323a7081f6SMaximilian Luz 
633c167b9c7SMaximilian Luz 	item->event.length = len;
6340d21bb85SMaximilian Luz 
6350d21bb85SMaximilian Luz 	trace_ssam_event_item_alloc(item, len);
636c167b9c7SMaximilian Luz 	return item;
637c167b9c7SMaximilian Luz }
638c167b9c7SMaximilian Luz 
639c167b9c7SMaximilian Luz /**
640c167b9c7SMaximilian Luz  * ssam_event_queue_push() - Push an event item to the event queue.
641c167b9c7SMaximilian Luz  * @q:    The event queue.
642c167b9c7SMaximilian Luz  * @item: The item to add.
643c167b9c7SMaximilian Luz  */
ssam_event_queue_push(struct ssam_event_queue * q,struct ssam_event_item * item)644c167b9c7SMaximilian Luz static void ssam_event_queue_push(struct ssam_event_queue *q,
645c167b9c7SMaximilian Luz 				  struct ssam_event_item *item)
646c167b9c7SMaximilian Luz {
647c167b9c7SMaximilian Luz 	spin_lock(&q->lock);
648c167b9c7SMaximilian Luz 	list_add_tail(&item->node, &q->head);
649c167b9c7SMaximilian Luz 	spin_unlock(&q->lock);
650c167b9c7SMaximilian Luz }
651c167b9c7SMaximilian Luz 
652c167b9c7SMaximilian Luz /**
653c167b9c7SMaximilian Luz  * ssam_event_queue_pop() - Pop the next event item from the event queue.
654c167b9c7SMaximilian Luz  * @q: The event queue.
655c167b9c7SMaximilian Luz  *
656c167b9c7SMaximilian Luz  * Returns and removes the next event item from the queue. Returns %NULL If
657c167b9c7SMaximilian Luz  * there is no event item left.
658c167b9c7SMaximilian Luz  */
ssam_event_queue_pop(struct ssam_event_queue * q)659c167b9c7SMaximilian Luz static struct ssam_event_item *ssam_event_queue_pop(struct ssam_event_queue *q)
660c167b9c7SMaximilian Luz {
661c167b9c7SMaximilian Luz 	struct ssam_event_item *item;
662c167b9c7SMaximilian Luz 
663c167b9c7SMaximilian Luz 	spin_lock(&q->lock);
664c167b9c7SMaximilian Luz 	item = list_first_entry_or_null(&q->head, struct ssam_event_item, node);
665c167b9c7SMaximilian Luz 	if (item)
666c167b9c7SMaximilian Luz 		list_del(&item->node);
667c167b9c7SMaximilian Luz 	spin_unlock(&q->lock);
668c167b9c7SMaximilian Luz 
669c167b9c7SMaximilian Luz 	return item;
670c167b9c7SMaximilian Luz }
671c167b9c7SMaximilian Luz 
672c167b9c7SMaximilian Luz /**
673c167b9c7SMaximilian Luz  * ssam_event_queue_is_empty() - Check if the event queue is empty.
674c167b9c7SMaximilian Luz  * @q: The event queue.
675c167b9c7SMaximilian Luz  */
ssam_event_queue_is_empty(struct ssam_event_queue * q)676c167b9c7SMaximilian Luz static bool ssam_event_queue_is_empty(struct ssam_event_queue *q)
677c167b9c7SMaximilian Luz {
678c167b9c7SMaximilian Luz 	bool empty;
679c167b9c7SMaximilian Luz 
680c167b9c7SMaximilian Luz 	spin_lock(&q->lock);
681c167b9c7SMaximilian Luz 	empty = list_empty(&q->head);
682c167b9c7SMaximilian Luz 	spin_unlock(&q->lock);
683c167b9c7SMaximilian Luz 
684c167b9c7SMaximilian Luz 	return empty;
685c167b9c7SMaximilian Luz }
686c167b9c7SMaximilian Luz 
687c167b9c7SMaximilian Luz /**
688c167b9c7SMaximilian Luz  * ssam_cplt_get_event_queue() - Get the event queue for the given parameters.
689c167b9c7SMaximilian Luz  * @cplt: The completion system on which to look for the queue.
690c167b9c7SMaximilian Luz  * @tid:  The target ID of the queue.
691c167b9c7SMaximilian Luz  * @rqid: The request ID representing the event ID for which to get the queue.
692c167b9c7SMaximilian Luz  *
693c167b9c7SMaximilian Luz  * Return: Returns the event queue corresponding to the event type described
694c167b9c7SMaximilian Luz  * by the given parameters. If the request ID does not represent an event,
695c167b9c7SMaximilian Luz  * this function returns %NULL. If the target ID is not supported, this
696c167b9c7SMaximilian Luz  * function will fall back to the default target ID (``tid = 1``).
697c167b9c7SMaximilian Luz  */
698c167b9c7SMaximilian Luz static
ssam_cplt_get_event_queue(struct ssam_cplt * cplt,u8 tid,u16 rqid)699c167b9c7SMaximilian Luz struct ssam_event_queue *ssam_cplt_get_event_queue(struct ssam_cplt *cplt,
700c167b9c7SMaximilian Luz 						   u8 tid, u16 rqid)
701c167b9c7SMaximilian Luz {
702c167b9c7SMaximilian Luz 	u16 event = ssh_rqid_to_event(rqid);
703c167b9c7SMaximilian Luz 	u16 tidx = ssh_tid_to_index(tid);
704c167b9c7SMaximilian Luz 
705c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid)) {
706c167b9c7SMaximilian Luz 		dev_err(cplt->dev, "event: unsupported request ID: %#06x\n", rqid);
707c167b9c7SMaximilian Luz 		return NULL;
708c167b9c7SMaximilian Luz 	}
709c167b9c7SMaximilian Luz 
710c167b9c7SMaximilian Luz 	if (!ssh_tid_is_valid(tid)) {
711c167b9c7SMaximilian Luz 		dev_warn(cplt->dev, "event: unsupported target ID: %u\n", tid);
712c167b9c7SMaximilian Luz 		tidx = 0;
713c167b9c7SMaximilian Luz 	}
714c167b9c7SMaximilian Luz 
715c167b9c7SMaximilian Luz 	return &cplt->event.target[tidx].queue[event];
716c167b9c7SMaximilian Luz }
717c167b9c7SMaximilian Luz 
718c167b9c7SMaximilian Luz /**
719c167b9c7SMaximilian Luz  * ssam_cplt_submit() - Submit a work item to the completion system workqueue.
720c167b9c7SMaximilian Luz  * @cplt: The completion system.
721c167b9c7SMaximilian Luz  * @work: The work item to submit.
722c167b9c7SMaximilian Luz  */
ssam_cplt_submit(struct ssam_cplt * cplt,struct work_struct * work)723c167b9c7SMaximilian Luz static bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work)
724c167b9c7SMaximilian Luz {
725c167b9c7SMaximilian Luz 	return queue_work(cplt->wq, work);
726c167b9c7SMaximilian Luz }
727c167b9c7SMaximilian Luz 
728c167b9c7SMaximilian Luz /**
729c167b9c7SMaximilian Luz  * ssam_cplt_submit_event() - Submit an event to the completion system.
730c167b9c7SMaximilian Luz  * @cplt: The completion system.
731c167b9c7SMaximilian Luz  * @item: The event item to submit.
732c167b9c7SMaximilian Luz  *
733c167b9c7SMaximilian Luz  * Submits the event to the completion system by queuing it on the event item
734c167b9c7SMaximilian Luz  * queue and queuing the respective event queue work item on the completion
735c167b9c7SMaximilian Luz  * workqueue, which will eventually complete the event.
736c167b9c7SMaximilian Luz  *
737c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-EINVAL if there is no event queue that
738c167b9c7SMaximilian Luz  * can handle the given event item.
739c167b9c7SMaximilian Luz  */
ssam_cplt_submit_event(struct ssam_cplt * cplt,struct ssam_event_item * item)740c167b9c7SMaximilian Luz static int ssam_cplt_submit_event(struct ssam_cplt *cplt,
741c167b9c7SMaximilian Luz 				  struct ssam_event_item *item)
742c167b9c7SMaximilian Luz {
743c167b9c7SMaximilian Luz 	struct ssam_event_queue *evq;
744c167b9c7SMaximilian Luz 
745c167b9c7SMaximilian Luz 	evq = ssam_cplt_get_event_queue(cplt, item->event.target_id, item->rqid);
746c167b9c7SMaximilian Luz 	if (!evq)
747c167b9c7SMaximilian Luz 		return -EINVAL;
748c167b9c7SMaximilian Luz 
749c167b9c7SMaximilian Luz 	ssam_event_queue_push(evq, item);
750c167b9c7SMaximilian Luz 	ssam_cplt_submit(cplt, &evq->work);
751c167b9c7SMaximilian Luz 	return 0;
752c167b9c7SMaximilian Luz }
753c167b9c7SMaximilian Luz 
754c167b9c7SMaximilian Luz /**
755c167b9c7SMaximilian Luz  * ssam_cplt_flush() - Flush the completion system.
756c167b9c7SMaximilian Luz  * @cplt: The completion system.
757c167b9c7SMaximilian Luz  *
758c167b9c7SMaximilian Luz  * Flush the completion system by waiting until all currently submitted work
759c167b9c7SMaximilian Luz  * items have been completed.
760c167b9c7SMaximilian Luz  *
761c167b9c7SMaximilian Luz  * Note: This function does not guarantee that all events will have been
762c167b9c7SMaximilian Luz  * handled once this call terminates. In case of a larger number of
763c167b9c7SMaximilian Luz  * to-be-completed events, the event queue work function may re-schedule its
764c167b9c7SMaximilian Luz  * work item, which this flush operation will ignore.
765c167b9c7SMaximilian Luz  *
766c167b9c7SMaximilian Luz  * This operation is only intended to, during normal operation prior to
767c167b9c7SMaximilian Luz  * shutdown, try to complete most events and requests to get them out of the
768c167b9c7SMaximilian Luz  * system while the system is still fully operational. It does not aim to
769c167b9c7SMaximilian Luz  * provide any guarantee that all of them have been handled.
770c167b9c7SMaximilian Luz  */
ssam_cplt_flush(struct ssam_cplt * cplt)771c167b9c7SMaximilian Luz static void ssam_cplt_flush(struct ssam_cplt *cplt)
772c167b9c7SMaximilian Luz {
773c167b9c7SMaximilian Luz 	flush_workqueue(cplt->wq);
774c167b9c7SMaximilian Luz }
775c167b9c7SMaximilian Luz 
ssam_event_queue_work_fn(struct work_struct * work)776c167b9c7SMaximilian Luz static void ssam_event_queue_work_fn(struct work_struct *work)
777c167b9c7SMaximilian Luz {
778c167b9c7SMaximilian Luz 	struct ssam_event_queue *queue;
779c167b9c7SMaximilian Luz 	struct ssam_event_item *item;
780c167b9c7SMaximilian Luz 	struct ssam_nf *nf;
781c167b9c7SMaximilian Luz 	struct device *dev;
782c167b9c7SMaximilian Luz 	unsigned int iterations = SSAM_CPLT_WQ_BATCH;
783c167b9c7SMaximilian Luz 
784c167b9c7SMaximilian Luz 	queue = container_of(work, struct ssam_event_queue, work);
785c167b9c7SMaximilian Luz 	nf = &queue->cplt->event.notif;
786c167b9c7SMaximilian Luz 	dev = queue->cplt->dev;
787c167b9c7SMaximilian Luz 
788c167b9c7SMaximilian Luz 	/* Limit number of processed events to avoid livelocking. */
789c167b9c7SMaximilian Luz 	do {
790c167b9c7SMaximilian Luz 		item = ssam_event_queue_pop(queue);
791c167b9c7SMaximilian Luz 		if (!item)
792c167b9c7SMaximilian Luz 			return;
793c167b9c7SMaximilian Luz 
794c167b9c7SMaximilian Luz 		ssam_nf_call(nf, dev, item->rqid, &item->event);
7953a7081f6SMaximilian Luz 		ssam_event_item_free(item);
796c167b9c7SMaximilian Luz 	} while (--iterations);
797c167b9c7SMaximilian Luz 
798c167b9c7SMaximilian Luz 	if (!ssam_event_queue_is_empty(queue))
799c167b9c7SMaximilian Luz 		ssam_cplt_submit(queue->cplt, &queue->work);
800c167b9c7SMaximilian Luz }
801c167b9c7SMaximilian Luz 
802c167b9c7SMaximilian Luz /**
803c167b9c7SMaximilian Luz  * ssam_event_queue_init() - Initialize an event queue.
804c167b9c7SMaximilian Luz  * @cplt: The completion system on which the queue resides.
805c167b9c7SMaximilian Luz  * @evq:  The event queue to initialize.
806c167b9c7SMaximilian Luz  */
ssam_event_queue_init(struct ssam_cplt * cplt,struct ssam_event_queue * evq)807c167b9c7SMaximilian Luz static void ssam_event_queue_init(struct ssam_cplt *cplt,
808c167b9c7SMaximilian Luz 				  struct ssam_event_queue *evq)
809c167b9c7SMaximilian Luz {
810c167b9c7SMaximilian Luz 	evq->cplt = cplt;
811c167b9c7SMaximilian Luz 	spin_lock_init(&evq->lock);
812c167b9c7SMaximilian Luz 	INIT_LIST_HEAD(&evq->head);
813c167b9c7SMaximilian Luz 	INIT_WORK(&evq->work, ssam_event_queue_work_fn);
814c167b9c7SMaximilian Luz }
815c167b9c7SMaximilian Luz 
816c167b9c7SMaximilian Luz /**
817c167b9c7SMaximilian Luz  * ssam_cplt_init() - Initialize completion system.
818c167b9c7SMaximilian Luz  * @cplt: The completion system to initialize.
819c167b9c7SMaximilian Luz  * @dev:  The device used for logging.
820c167b9c7SMaximilian Luz  */
ssam_cplt_init(struct ssam_cplt * cplt,struct device * dev)821c167b9c7SMaximilian Luz static int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev)
822c167b9c7SMaximilian Luz {
823c167b9c7SMaximilian Luz 	struct ssam_event_target *target;
824c167b9c7SMaximilian Luz 	int status, c, i;
825c167b9c7SMaximilian Luz 
826c167b9c7SMaximilian Luz 	cplt->dev = dev;
827c167b9c7SMaximilian Luz 
828*539e0a7fSMaximilian Luz 	cplt->wq = alloc_workqueue(SSAM_CPLT_WQ_NAME, WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
829c167b9c7SMaximilian Luz 	if (!cplt->wq)
830c167b9c7SMaximilian Luz 		return -ENOMEM;
831c167b9c7SMaximilian Luz 
832c167b9c7SMaximilian Luz 	for (c = 0; c < ARRAY_SIZE(cplt->event.target); c++) {
833c167b9c7SMaximilian Luz 		target = &cplt->event.target[c];
834c167b9c7SMaximilian Luz 
835c167b9c7SMaximilian Luz 		for (i = 0; i < ARRAY_SIZE(target->queue); i++)
836c167b9c7SMaximilian Luz 			ssam_event_queue_init(cplt, &target->queue[i]);
837c167b9c7SMaximilian Luz 	}
838c167b9c7SMaximilian Luz 
839c167b9c7SMaximilian Luz 	status = ssam_nf_init(&cplt->event.notif);
840c167b9c7SMaximilian Luz 	if (status)
841c167b9c7SMaximilian Luz 		destroy_workqueue(cplt->wq);
842c167b9c7SMaximilian Luz 
843c167b9c7SMaximilian Luz 	return status;
844c167b9c7SMaximilian Luz }
845c167b9c7SMaximilian Luz 
846c167b9c7SMaximilian Luz /**
847c167b9c7SMaximilian Luz  * ssam_cplt_destroy() - Deinitialize the completion system.
848c167b9c7SMaximilian Luz  * @cplt: The completion system to deinitialize.
849c167b9c7SMaximilian Luz  *
850c167b9c7SMaximilian Luz  * Deinitialize the given completion system and ensure that all pending, i.e.
851c167b9c7SMaximilian Luz  * yet-to-be-completed, event items and requests have been handled.
852c167b9c7SMaximilian Luz  */
ssam_cplt_destroy(struct ssam_cplt * cplt)853c167b9c7SMaximilian Luz static void ssam_cplt_destroy(struct ssam_cplt *cplt)
854c167b9c7SMaximilian Luz {
855c167b9c7SMaximilian Luz 	/*
856c167b9c7SMaximilian Luz 	 * Note: destroy_workqueue ensures that all currently queued work will
857c167b9c7SMaximilian Luz 	 * be fully completed and the workqueue drained. This means that this
858c167b9c7SMaximilian Luz 	 * call will inherently also free any queued ssam_event_items, thus we
859c167b9c7SMaximilian Luz 	 * don't have to take care of that here explicitly.
860c167b9c7SMaximilian Luz 	 */
861c167b9c7SMaximilian Luz 	destroy_workqueue(cplt->wq);
862c167b9c7SMaximilian Luz 	ssam_nf_destroy(&cplt->event.notif);
863c167b9c7SMaximilian Luz }
864c167b9c7SMaximilian Luz 
865c167b9c7SMaximilian Luz 
866c167b9c7SMaximilian Luz /* -- Main SSAM device structures. ------------------------------------------ */
867c167b9c7SMaximilian Luz 
868c167b9c7SMaximilian Luz /**
869c167b9c7SMaximilian Luz  * ssam_controller_device() - Get the &struct device associated with this
870c167b9c7SMaximilian Luz  * controller.
871c167b9c7SMaximilian Luz  * @c: The controller for which to get the device.
872c167b9c7SMaximilian Luz  *
873c167b9c7SMaximilian Luz  * Return: Returns the &struct device associated with this controller,
874c167b9c7SMaximilian Luz  * providing its lower-level transport.
875c167b9c7SMaximilian Luz  */
ssam_controller_device(struct ssam_controller * c)876c167b9c7SMaximilian Luz struct device *ssam_controller_device(struct ssam_controller *c)
877c167b9c7SMaximilian Luz {
878c167b9c7SMaximilian Luz 	return ssh_rtl_get_device(&c->rtl);
879c167b9c7SMaximilian Luz }
880c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_device);
881c167b9c7SMaximilian Luz 
__ssam_controller_release(struct kref * kref)882c167b9c7SMaximilian Luz static void __ssam_controller_release(struct kref *kref)
883c167b9c7SMaximilian Luz {
884c167b9c7SMaximilian Luz 	struct ssam_controller *ctrl = to_ssam_controller(kref, kref);
885c167b9c7SMaximilian Luz 
886c167b9c7SMaximilian Luz 	/*
887c167b9c7SMaximilian Luz 	 * The lock-call here is to satisfy lockdep. At this point we really
888c167b9c7SMaximilian Luz 	 * expect this to be the last remaining reference to the controller.
889c167b9c7SMaximilian Luz 	 * Anything else is a bug.
890c167b9c7SMaximilian Luz 	 */
891c167b9c7SMaximilian Luz 	ssam_controller_lock(ctrl);
892c167b9c7SMaximilian Luz 	ssam_controller_destroy(ctrl);
893c167b9c7SMaximilian Luz 	ssam_controller_unlock(ctrl);
894c167b9c7SMaximilian Luz 
895c167b9c7SMaximilian Luz 	kfree(ctrl);
896c167b9c7SMaximilian Luz }
897c167b9c7SMaximilian Luz 
898c167b9c7SMaximilian Luz /**
899c167b9c7SMaximilian Luz  * ssam_controller_get() - Increment reference count of controller.
900c167b9c7SMaximilian Luz  * @c: The controller.
901c167b9c7SMaximilian Luz  *
902c167b9c7SMaximilian Luz  * Return: Returns the controller provided as input.
903c167b9c7SMaximilian Luz  */
ssam_controller_get(struct ssam_controller * c)904c167b9c7SMaximilian Luz struct ssam_controller *ssam_controller_get(struct ssam_controller *c)
905c167b9c7SMaximilian Luz {
906c167b9c7SMaximilian Luz 	if (c)
907c167b9c7SMaximilian Luz 		kref_get(&c->kref);
908c167b9c7SMaximilian Luz 	return c;
909c167b9c7SMaximilian Luz }
910c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_get);
911c167b9c7SMaximilian Luz 
912c167b9c7SMaximilian Luz /**
913c167b9c7SMaximilian Luz  * ssam_controller_put() - Decrement reference count of controller.
914c167b9c7SMaximilian Luz  * @c: The controller.
915c167b9c7SMaximilian Luz  */
ssam_controller_put(struct ssam_controller * c)916c167b9c7SMaximilian Luz void ssam_controller_put(struct ssam_controller *c)
917c167b9c7SMaximilian Luz {
918c167b9c7SMaximilian Luz 	if (c)
919c167b9c7SMaximilian Luz 		kref_put(&c->kref, __ssam_controller_release);
920c167b9c7SMaximilian Luz }
921c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_put);
922c167b9c7SMaximilian Luz 
923c167b9c7SMaximilian Luz /**
924c167b9c7SMaximilian Luz  * ssam_controller_statelock() - Lock the controller against state transitions.
925c167b9c7SMaximilian Luz  * @c: The controller to lock.
926c167b9c7SMaximilian Luz  *
927c167b9c7SMaximilian Luz  * Lock the controller against state transitions. Holding this lock guarantees
928c167b9c7SMaximilian Luz  * that the controller will not transition between states, i.e. if the
929c167b9c7SMaximilian Luz  * controller is in state "started", when this lock has been acquired, it will
930c167b9c7SMaximilian Luz  * remain in this state at least until the lock has been released.
931c167b9c7SMaximilian Luz  *
932c167b9c7SMaximilian Luz  * Multiple clients may concurrently hold this lock. In other words: The
933c167b9c7SMaximilian Luz  * ``statelock`` functions represent the read-lock part of a r/w-semaphore.
934c167b9c7SMaximilian Luz  * Actions causing state transitions of the controller must be executed while
935c167b9c7SMaximilian Luz  * holding the write-part of this r/w-semaphore (see ssam_controller_lock()
936c167b9c7SMaximilian Luz  * and ssam_controller_unlock() for that).
937c167b9c7SMaximilian Luz  *
938c167b9c7SMaximilian Luz  * See ssam_controller_stateunlock() for the corresponding unlock function.
939c167b9c7SMaximilian Luz  */
ssam_controller_statelock(struct ssam_controller * c)940c167b9c7SMaximilian Luz void ssam_controller_statelock(struct ssam_controller *c)
941c167b9c7SMaximilian Luz {
942c167b9c7SMaximilian Luz 	down_read(&c->lock);
943c167b9c7SMaximilian Luz }
944c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_statelock);
945c167b9c7SMaximilian Luz 
946c167b9c7SMaximilian Luz /**
947c167b9c7SMaximilian Luz  * ssam_controller_stateunlock() - Unlock controller state transitions.
948c167b9c7SMaximilian Luz  * @c: The controller to unlock.
949c167b9c7SMaximilian Luz  *
950c167b9c7SMaximilian Luz  * See ssam_controller_statelock() for the corresponding lock function.
951c167b9c7SMaximilian Luz  */
ssam_controller_stateunlock(struct ssam_controller * c)952c167b9c7SMaximilian Luz void ssam_controller_stateunlock(struct ssam_controller *c)
953c167b9c7SMaximilian Luz {
954c167b9c7SMaximilian Luz 	up_read(&c->lock);
955c167b9c7SMaximilian Luz }
956c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_stateunlock);
957c167b9c7SMaximilian Luz 
958c167b9c7SMaximilian Luz /**
959c167b9c7SMaximilian Luz  * ssam_controller_lock() - Acquire the main controller lock.
960c167b9c7SMaximilian Luz  * @c: The controller to lock.
961c167b9c7SMaximilian Luz  *
962c167b9c7SMaximilian Luz  * This lock must be held for any state transitions, including transition to
963c167b9c7SMaximilian Luz  * suspend/resumed states and during shutdown. See ssam_controller_statelock()
964c167b9c7SMaximilian Luz  * for more details on controller locking.
965c167b9c7SMaximilian Luz  *
966c167b9c7SMaximilian Luz  * See ssam_controller_unlock() for the corresponding unlock function.
967c167b9c7SMaximilian Luz  */
ssam_controller_lock(struct ssam_controller * c)968c167b9c7SMaximilian Luz void ssam_controller_lock(struct ssam_controller *c)
969c167b9c7SMaximilian Luz {
970c167b9c7SMaximilian Luz 	down_write(&c->lock);
971c167b9c7SMaximilian Luz }
972c167b9c7SMaximilian Luz 
973c167b9c7SMaximilian Luz /*
974c167b9c7SMaximilian Luz  * ssam_controller_unlock() - Release the main controller lock.
975c167b9c7SMaximilian Luz  * @c: The controller to unlock.
976c167b9c7SMaximilian Luz  *
977c167b9c7SMaximilian Luz  * See ssam_controller_lock() for the corresponding lock function.
978c167b9c7SMaximilian Luz  */
ssam_controller_unlock(struct ssam_controller * c)979c167b9c7SMaximilian Luz void ssam_controller_unlock(struct ssam_controller *c)
980c167b9c7SMaximilian Luz {
981c167b9c7SMaximilian Luz 	up_write(&c->lock);
982c167b9c7SMaximilian Luz }
983c167b9c7SMaximilian Luz 
ssam_handle_event(struct ssh_rtl * rtl,const struct ssh_command * cmd,const struct ssam_span * data)984c167b9c7SMaximilian Luz static void ssam_handle_event(struct ssh_rtl *rtl,
985c167b9c7SMaximilian Luz 			      const struct ssh_command *cmd,
986c167b9c7SMaximilian Luz 			      const struct ssam_span *data)
987c167b9c7SMaximilian Luz {
988c167b9c7SMaximilian Luz 	struct ssam_controller *ctrl = to_ssam_controller(rtl, rtl);
989c167b9c7SMaximilian Luz 	struct ssam_event_item *item;
990c167b9c7SMaximilian Luz 
991c167b9c7SMaximilian Luz 	item = ssam_event_item_alloc(data->len, GFP_KERNEL);
992c167b9c7SMaximilian Luz 	if (!item)
993c167b9c7SMaximilian Luz 		return;
994c167b9c7SMaximilian Luz 
995c167b9c7SMaximilian Luz 	item->rqid = get_unaligned_le16(&cmd->rqid);
996c167b9c7SMaximilian Luz 	item->event.target_category = cmd->tc;
9973f88b459SMaximilian Luz 	item->event.target_id = cmd->sid;
998c167b9c7SMaximilian Luz 	item->event.command_id = cmd->cid;
999c167b9c7SMaximilian Luz 	item->event.instance_id = cmd->iid;
1000c167b9c7SMaximilian Luz 	memcpy(&item->event.data[0], data->ptr, data->len);
1001c167b9c7SMaximilian Luz 
1002c167b9c7SMaximilian Luz 	if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item)))
10033a7081f6SMaximilian Luz 		ssam_event_item_free(item);
1004c167b9c7SMaximilian Luz }
1005c167b9c7SMaximilian Luz 
1006c167b9c7SMaximilian Luz static const struct ssh_rtl_ops ssam_rtl_ops = {
1007c167b9c7SMaximilian Luz 	.handle_event = ssam_handle_event,
1008c167b9c7SMaximilian Luz };
1009c167b9c7SMaximilian Luz 
1010c167b9c7SMaximilian Luz static bool ssam_notifier_is_empty(struct ssam_controller *ctrl);
1011c167b9c7SMaximilian Luz static void ssam_notifier_unregister_all(struct ssam_controller *ctrl);
1012c167b9c7SMaximilian Luz 
1013c167b9c7SMaximilian Luz #define SSAM_SSH_DSM_REVISION	0
1014c167b9c7SMaximilian Luz 
1015c167b9c7SMaximilian Luz /* d5e383e1-d892-4a76-89fc-f6aaae7ed5b5 */
1016c167b9c7SMaximilian Luz static const guid_t SSAM_SSH_DSM_GUID =
1017c167b9c7SMaximilian Luz 	GUID_INIT(0xd5e383e1, 0xd892, 0x4a76,
1018c167b9c7SMaximilian Luz 		  0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5);
1019c167b9c7SMaximilian Luz 
1020c167b9c7SMaximilian Luz enum ssh_dsm_fn {
1021c167b9c7SMaximilian Luz 	SSH_DSM_FN_SSH_POWER_PROFILE             = 0x05,
1022c167b9c7SMaximilian Luz 	SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT  = 0x06,
1023c167b9c7SMaximilian Luz 	SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07,
1024c167b9c7SMaximilian Luz 	SSH_DSM_FN_D3_CLOSES_HANDLE              = 0x08,
1025c167b9c7SMaximilian Luz 	SSH_DSM_FN_SSH_BUFFER_SIZE               = 0x09,
1026c167b9c7SMaximilian Luz };
1027c167b9c7SMaximilian Luz 
ssam_dsm_get_functions(acpi_handle handle,u64 * funcs)1028c167b9c7SMaximilian Luz static int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs)
1029c167b9c7SMaximilian Luz {
1030c167b9c7SMaximilian Luz 	union acpi_object *obj;
1031c167b9c7SMaximilian Luz 	u64 mask = 0;
1032c167b9c7SMaximilian Luz 	int i;
1033c167b9c7SMaximilian Luz 
1034c167b9c7SMaximilian Luz 	*funcs = 0;
1035c167b9c7SMaximilian Luz 
1036c167b9c7SMaximilian Luz 	/*
1037c167b9c7SMaximilian Luz 	 * The _DSM function is only present on newer models. It is not
1038c167b9c7SMaximilian Luz 	 * present on 5th and 6th generation devices (i.e. up to and including
1039c167b9c7SMaximilian Luz 	 * Surface Pro 6, Surface Laptop 2, Surface Book 2).
1040c167b9c7SMaximilian Luz 	 *
1041c167b9c7SMaximilian Luz 	 * If the _DSM is not present, indicate that no function is supported.
1042c167b9c7SMaximilian Luz 	 * This will result in default values being set.
1043c167b9c7SMaximilian Luz 	 */
1044c167b9c7SMaximilian Luz 	if (!acpi_has_method(handle, "_DSM"))
1045c167b9c7SMaximilian Luz 		return 0;
1046c167b9c7SMaximilian Luz 
1047c167b9c7SMaximilian Luz 	obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID,
1048c167b9c7SMaximilian Luz 				      SSAM_SSH_DSM_REVISION, 0, NULL,
1049c167b9c7SMaximilian Luz 				      ACPI_TYPE_BUFFER);
1050c167b9c7SMaximilian Luz 	if (!obj)
1051c167b9c7SMaximilian Luz 		return -EIO;
1052c167b9c7SMaximilian Luz 
1053c167b9c7SMaximilian Luz 	for (i = 0; i < obj->buffer.length && i < 8; i++)
1054c167b9c7SMaximilian Luz 		mask |= (((u64)obj->buffer.pointer[i]) << (i * 8));
1055c167b9c7SMaximilian Luz 
1056c167b9c7SMaximilian Luz 	if (mask & BIT(0))
1057c167b9c7SMaximilian Luz 		*funcs = mask;
1058c167b9c7SMaximilian Luz 
1059c167b9c7SMaximilian Luz 	ACPI_FREE(obj);
1060c167b9c7SMaximilian Luz 	return 0;
1061c167b9c7SMaximilian Luz }
1062c167b9c7SMaximilian Luz 
ssam_dsm_load_u32(acpi_handle handle,u64 funcs,u64 func,u32 * ret)1063c167b9c7SMaximilian Luz static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret)
1064c167b9c7SMaximilian Luz {
1065c167b9c7SMaximilian Luz 	union acpi_object *obj;
1066c167b9c7SMaximilian Luz 	u64 val;
1067c167b9c7SMaximilian Luz 
1068366f0a30SDan Carpenter 	if (!(funcs & BIT_ULL(func)))
1069c167b9c7SMaximilian Luz 		return 0; /* Not supported, leave *ret at its default value */
1070c167b9c7SMaximilian Luz 
1071c167b9c7SMaximilian Luz 	obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID,
1072c167b9c7SMaximilian Luz 				      SSAM_SSH_DSM_REVISION, func, NULL,
1073c167b9c7SMaximilian Luz 				      ACPI_TYPE_INTEGER);
1074c167b9c7SMaximilian Luz 	if (!obj)
1075c167b9c7SMaximilian Luz 		return -EIO;
1076c167b9c7SMaximilian Luz 
1077c167b9c7SMaximilian Luz 	val = obj->integer.value;
1078c167b9c7SMaximilian Luz 	ACPI_FREE(obj);
1079c167b9c7SMaximilian Luz 
1080c167b9c7SMaximilian Luz 	if (val > U32_MAX)
1081c167b9c7SMaximilian Luz 		return -ERANGE;
1082c167b9c7SMaximilian Luz 
1083c167b9c7SMaximilian Luz 	*ret = val;
1084c167b9c7SMaximilian Luz 	return 0;
1085c167b9c7SMaximilian Luz }
1086c167b9c7SMaximilian Luz 
1087c167b9c7SMaximilian Luz /**
1088c167b9c7SMaximilian Luz  * ssam_controller_caps_load_from_acpi() - Load controller capabilities from
1089c167b9c7SMaximilian Luz  * ACPI _DSM.
1090c167b9c7SMaximilian Luz  * @handle: The handle of the ACPI controller/SSH device.
1091c167b9c7SMaximilian Luz  * @caps:   Where to store the capabilities in.
1092c167b9c7SMaximilian Luz  *
1093c167b9c7SMaximilian Luz  * Initializes the given controller capabilities with default values, then
1094c167b9c7SMaximilian Luz  * checks and, if the respective _DSM functions are available, loads the
1095c167b9c7SMaximilian Luz  * actual capabilities from the _DSM.
1096c167b9c7SMaximilian Luz  *
1097c167b9c7SMaximilian Luz  * Return: Returns zero on success, a negative error code on failure.
1098c167b9c7SMaximilian Luz  */
1099c167b9c7SMaximilian Luz static
ssam_controller_caps_load_from_acpi(acpi_handle handle,struct ssam_controller_caps * caps)1100c167b9c7SMaximilian Luz int ssam_controller_caps_load_from_acpi(acpi_handle handle,
1101c167b9c7SMaximilian Luz 					struct ssam_controller_caps *caps)
1102c167b9c7SMaximilian Luz {
1103c167b9c7SMaximilian Luz 	u32 d3_closes_handle = false;
1104c167b9c7SMaximilian Luz 	u64 funcs;
1105c167b9c7SMaximilian Luz 	int status;
1106c167b9c7SMaximilian Luz 
1107c167b9c7SMaximilian Luz 	/* Set defaults. */
1108c167b9c7SMaximilian Luz 	caps->ssh_power_profile = U32_MAX;
1109c167b9c7SMaximilian Luz 	caps->screen_on_sleep_idle_timeout = U32_MAX;
1110c167b9c7SMaximilian Luz 	caps->screen_off_sleep_idle_timeout = U32_MAX;
1111c167b9c7SMaximilian Luz 	caps->d3_closes_handle = false;
1112c167b9c7SMaximilian Luz 	caps->ssh_buffer_size = U32_MAX;
1113c167b9c7SMaximilian Luz 
1114c167b9c7SMaximilian Luz 	/* Pre-load supported DSM functions. */
1115c167b9c7SMaximilian Luz 	status = ssam_dsm_get_functions(handle, &funcs);
1116c167b9c7SMaximilian Luz 	if (status)
1117c167b9c7SMaximilian Luz 		return status;
1118c167b9c7SMaximilian Luz 
1119c167b9c7SMaximilian Luz 	/* Load actual values from ACPI, if present. */
1120c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE,
1121c167b9c7SMaximilian Luz 				   &caps->ssh_power_profile);
1122c167b9c7SMaximilian Luz 	if (status)
1123c167b9c7SMaximilian Luz 		return status;
1124c167b9c7SMaximilian Luz 
1125c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs,
1126c167b9c7SMaximilian Luz 				   SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT,
1127c167b9c7SMaximilian Luz 				   &caps->screen_on_sleep_idle_timeout);
1128c167b9c7SMaximilian Luz 	if (status)
1129c167b9c7SMaximilian Luz 		return status;
1130c167b9c7SMaximilian Luz 
1131c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs,
1132c167b9c7SMaximilian Luz 				   SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT,
1133c167b9c7SMaximilian Luz 				   &caps->screen_off_sleep_idle_timeout);
1134c167b9c7SMaximilian Luz 	if (status)
1135c167b9c7SMaximilian Luz 		return status;
1136c167b9c7SMaximilian Luz 
1137c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE,
1138c167b9c7SMaximilian Luz 				   &d3_closes_handle);
1139c167b9c7SMaximilian Luz 	if (status)
1140c167b9c7SMaximilian Luz 		return status;
1141c167b9c7SMaximilian Luz 
1142c167b9c7SMaximilian Luz 	caps->d3_closes_handle = !!d3_closes_handle;
1143c167b9c7SMaximilian Luz 
1144c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE,
1145c167b9c7SMaximilian Luz 				   &caps->ssh_buffer_size);
1146c167b9c7SMaximilian Luz 	if (status)
1147c167b9c7SMaximilian Luz 		return status;
1148c167b9c7SMaximilian Luz 
1149c167b9c7SMaximilian Luz 	return 0;
1150c167b9c7SMaximilian Luz }
1151c167b9c7SMaximilian Luz 
1152c167b9c7SMaximilian Luz /**
1153c167b9c7SMaximilian Luz  * ssam_controller_init() - Initialize SSAM controller.
1154c167b9c7SMaximilian Luz  * @ctrl:   The controller to initialize.
1155c167b9c7SMaximilian Luz  * @serdev: The serial device representing the underlying data transport.
1156c167b9c7SMaximilian Luz  *
1157c167b9c7SMaximilian Luz  * Initializes the given controller. Does neither start receiver nor
1158c167b9c7SMaximilian Luz  * transmitter threads. After this call, the controller has to be hooked up to
1159c167b9c7SMaximilian Luz  * the serdev core separately via &struct serdev_device_ops, relaying calls to
1160c167b9c7SMaximilian Luz  * ssam_controller_receive_buf() and ssam_controller_write_wakeup(). Once the
1161c167b9c7SMaximilian Luz  * controller has been hooked up, transmitter and receiver threads may be
1162c167b9c7SMaximilian Luz  * started via ssam_controller_start(). These setup steps need to be completed
1163c167b9c7SMaximilian Luz  * before controller can be used for requests.
1164c167b9c7SMaximilian Luz  */
ssam_controller_init(struct ssam_controller * ctrl,struct serdev_device * serdev)1165c167b9c7SMaximilian Luz int ssam_controller_init(struct ssam_controller *ctrl,
1166c167b9c7SMaximilian Luz 			 struct serdev_device *serdev)
1167c167b9c7SMaximilian Luz {
1168c167b9c7SMaximilian Luz 	acpi_handle handle = ACPI_HANDLE(&serdev->dev);
1169c167b9c7SMaximilian Luz 	int status;
1170c167b9c7SMaximilian Luz 
1171c167b9c7SMaximilian Luz 	init_rwsem(&ctrl->lock);
1172c167b9c7SMaximilian Luz 	kref_init(&ctrl->kref);
1173c167b9c7SMaximilian Luz 
1174c167b9c7SMaximilian Luz 	status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps);
1175c167b9c7SMaximilian Luz 	if (status)
1176c167b9c7SMaximilian Luz 		return status;
1177c167b9c7SMaximilian Luz 
1178c167b9c7SMaximilian Luz 	dev_dbg(&serdev->dev,
1179c167b9c7SMaximilian Luz 		"device capabilities:\n"
1180c167b9c7SMaximilian Luz 		"  ssh_power_profile:             %u\n"
1181c167b9c7SMaximilian Luz 		"  ssh_buffer_size:               %u\n"
1182c167b9c7SMaximilian Luz 		"  screen_on_sleep_idle_timeout:  %u\n"
1183c167b9c7SMaximilian Luz 		"  screen_off_sleep_idle_timeout: %u\n"
1184c167b9c7SMaximilian Luz 		"  d3_closes_handle:              %u\n",
1185c167b9c7SMaximilian Luz 		ctrl->caps.ssh_power_profile,
1186c167b9c7SMaximilian Luz 		ctrl->caps.ssh_buffer_size,
1187c167b9c7SMaximilian Luz 		ctrl->caps.screen_on_sleep_idle_timeout,
1188c167b9c7SMaximilian Luz 		ctrl->caps.screen_off_sleep_idle_timeout,
1189c167b9c7SMaximilian Luz 		ctrl->caps.d3_closes_handle);
1190c167b9c7SMaximilian Luz 
1191c167b9c7SMaximilian Luz 	ssh_seq_reset(&ctrl->counter.seq);
1192c167b9c7SMaximilian Luz 	ssh_rqid_reset(&ctrl->counter.rqid);
1193c167b9c7SMaximilian Luz 
1194c167b9c7SMaximilian Luz 	/* Initialize event/request completion system. */
1195c167b9c7SMaximilian Luz 	status = ssam_cplt_init(&ctrl->cplt, &serdev->dev);
1196c167b9c7SMaximilian Luz 	if (status)
1197c167b9c7SMaximilian Luz 		return status;
1198c167b9c7SMaximilian Luz 
1199c167b9c7SMaximilian Luz 	/* Initialize request and packet transport layers. */
1200c167b9c7SMaximilian Luz 	status = ssh_rtl_init(&ctrl->rtl, serdev, &ssam_rtl_ops);
1201c167b9c7SMaximilian Luz 	if (status) {
1202c167b9c7SMaximilian Luz 		ssam_cplt_destroy(&ctrl->cplt);
1203c167b9c7SMaximilian Luz 		return status;
1204c167b9c7SMaximilian Luz 	}
1205c167b9c7SMaximilian Luz 
1206c167b9c7SMaximilian Luz 	/*
1207c167b9c7SMaximilian Luz 	 * Set state via write_once even though we expect to be in an
1208c167b9c7SMaximilian Luz 	 * exclusive context, due to smoke-testing in
1209c167b9c7SMaximilian Luz 	 * ssam_request_sync_submit().
1210c167b9c7SMaximilian Luz 	 */
1211c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_INITIALIZED);
1212c167b9c7SMaximilian Luz 	return 0;
1213c167b9c7SMaximilian Luz }
1214c167b9c7SMaximilian Luz 
1215c167b9c7SMaximilian Luz /**
1216c167b9c7SMaximilian Luz  * ssam_controller_start() - Start the receiver and transmitter threads of the
1217c167b9c7SMaximilian Luz  * controller.
1218c167b9c7SMaximilian Luz  * @ctrl: The controller.
1219c167b9c7SMaximilian Luz  *
1220c167b9c7SMaximilian Luz  * Note: When this function is called, the controller should be properly
1221c167b9c7SMaximilian Luz  * hooked up to the serdev core via &struct serdev_device_ops. Please refer
1222c167b9c7SMaximilian Luz  * to ssam_controller_init() for more details on controller initialization.
1223c167b9c7SMaximilian Luz  *
1224c167b9c7SMaximilian Luz  * This function must be called with the main controller lock held (i.e. by
1225c167b9c7SMaximilian Luz  * calling ssam_controller_lock()).
1226c167b9c7SMaximilian Luz  */
ssam_controller_start(struct ssam_controller * ctrl)1227c167b9c7SMaximilian Luz int ssam_controller_start(struct ssam_controller *ctrl)
1228c167b9c7SMaximilian Luz {
1229c167b9c7SMaximilian Luz 	int status;
1230c167b9c7SMaximilian Luz 
1231c167b9c7SMaximilian Luz 	lockdep_assert_held_write(&ctrl->lock);
1232c167b9c7SMaximilian Luz 
1233c167b9c7SMaximilian Luz 	if (ctrl->state != SSAM_CONTROLLER_INITIALIZED)
1234c167b9c7SMaximilian Luz 		return -EINVAL;
1235c167b9c7SMaximilian Luz 
1236c167b9c7SMaximilian Luz 	status = ssh_rtl_start(&ctrl->rtl);
1237c167b9c7SMaximilian Luz 	if (status)
1238c167b9c7SMaximilian Luz 		return status;
1239c167b9c7SMaximilian Luz 
1240c167b9c7SMaximilian Luz 	/*
1241c167b9c7SMaximilian Luz 	 * Set state via write_once even though we expect to be locked/in an
1242c167b9c7SMaximilian Luz 	 * exclusive context, due to smoke-testing in
1243c167b9c7SMaximilian Luz 	 * ssam_request_sync_submit().
1244c167b9c7SMaximilian Luz 	 */
1245c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED);
1246c167b9c7SMaximilian Luz 	return 0;
1247c167b9c7SMaximilian Luz }
1248c167b9c7SMaximilian Luz 
1249c167b9c7SMaximilian Luz /*
1250c167b9c7SMaximilian Luz  * SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT - Timeout for flushing requests during
1251c167b9c7SMaximilian Luz  * shutdown.
1252c167b9c7SMaximilian Luz  *
1253c167b9c7SMaximilian Luz  * Chosen to be larger than one full request timeout, including packets timing
1254c167b9c7SMaximilian Luz  * out. This value should give ample time to complete any outstanding requests
1255c167b9c7SMaximilian Luz  * during normal operation and account for the odd package timeout.
1256c167b9c7SMaximilian Luz  */
1257c167b9c7SMaximilian Luz #define SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT	msecs_to_jiffies(5000)
1258c167b9c7SMaximilian Luz 
1259c167b9c7SMaximilian Luz /**
1260c167b9c7SMaximilian Luz  * ssam_controller_shutdown() - Shut down the controller.
1261c167b9c7SMaximilian Luz  * @ctrl: The controller.
1262c167b9c7SMaximilian Luz  *
1263c167b9c7SMaximilian Luz  * Shuts down the controller by flushing all pending requests and stopping the
1264c167b9c7SMaximilian Luz  * transmitter and receiver threads. All requests submitted after this call
1265c167b9c7SMaximilian Luz  * will fail with %-ESHUTDOWN. While it is discouraged to do so, this function
1266c167b9c7SMaximilian Luz  * is safe to use in parallel with ongoing request submission.
1267c167b9c7SMaximilian Luz  *
1268c167b9c7SMaximilian Luz  * In the course of this shutdown procedure, all currently registered
1269c167b9c7SMaximilian Luz  * notifiers will be unregistered. It is, however, strongly recommended to not
1270c167b9c7SMaximilian Luz  * rely on this behavior, and instead the party registering the notifier
1271c167b9c7SMaximilian Luz  * should unregister it before the controller gets shut down, e.g. via the
1272c167b9c7SMaximilian Luz  * SSAM bus which guarantees client devices to be removed before a shutdown.
1273c167b9c7SMaximilian Luz  *
1274c167b9c7SMaximilian Luz  * Note that events may still be pending after this call, but, due to the
1275c167b9c7SMaximilian Luz  * notifiers being unregistered, these events will be dropped when the
1276c167b9c7SMaximilian Luz  * controller is subsequently destroyed via ssam_controller_destroy().
1277c167b9c7SMaximilian Luz  *
1278c167b9c7SMaximilian Luz  * This function must be called with the main controller lock held (i.e. by
1279c167b9c7SMaximilian Luz  * calling ssam_controller_lock()).
1280c167b9c7SMaximilian Luz  */
ssam_controller_shutdown(struct ssam_controller * ctrl)1281c167b9c7SMaximilian Luz void ssam_controller_shutdown(struct ssam_controller *ctrl)
1282c167b9c7SMaximilian Luz {
1283c167b9c7SMaximilian Luz 	enum ssam_controller_state s = ctrl->state;
1284c167b9c7SMaximilian Luz 	int status;
1285c167b9c7SMaximilian Luz 
1286c167b9c7SMaximilian Luz 	lockdep_assert_held_write(&ctrl->lock);
1287c167b9c7SMaximilian Luz 
1288c167b9c7SMaximilian Luz 	if (s == SSAM_CONTROLLER_UNINITIALIZED || s == SSAM_CONTROLLER_STOPPED)
1289c167b9c7SMaximilian Luz 		return;
1290c167b9c7SMaximilian Luz 
1291c167b9c7SMaximilian Luz 	/*
1292c167b9c7SMaximilian Luz 	 * Try to flush pending events and requests while everything still
1293c167b9c7SMaximilian Luz 	 * works. Note: There may still be packets and/or requests in the
1294c167b9c7SMaximilian Luz 	 * system after this call (e.g. via control packets submitted by the
1295c167b9c7SMaximilian Luz 	 * packet transport layer or flush timeout / failure, ...). Those will
1296c167b9c7SMaximilian Luz 	 * be handled with the ssh_rtl_shutdown() call below.
1297c167b9c7SMaximilian Luz 	 */
1298c167b9c7SMaximilian Luz 	status = ssh_rtl_flush(&ctrl->rtl, SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT);
1299c167b9c7SMaximilian Luz 	if (status) {
1300c167b9c7SMaximilian Luz 		ssam_err(ctrl, "failed to flush request transport layer: %d\n",
1301c167b9c7SMaximilian Luz 			 status);
1302c167b9c7SMaximilian Luz 	}
1303c167b9c7SMaximilian Luz 
1304c167b9c7SMaximilian Luz 	/* Try to flush all currently completing requests and events. */
1305c167b9c7SMaximilian Luz 	ssam_cplt_flush(&ctrl->cplt);
1306c167b9c7SMaximilian Luz 
1307c167b9c7SMaximilian Luz 	/*
1308c167b9c7SMaximilian Luz 	 * We expect all notifiers to have been removed by the respective client
1309c167b9c7SMaximilian Luz 	 * driver that set them up at this point. If this warning occurs, some
1310c167b9c7SMaximilian Luz 	 * client driver has not done that...
1311c167b9c7SMaximilian Luz 	 */
1312c167b9c7SMaximilian Luz 	WARN_ON(!ssam_notifier_is_empty(ctrl));
1313c167b9c7SMaximilian Luz 
1314c167b9c7SMaximilian Luz 	/*
1315c167b9c7SMaximilian Luz 	 * Nevertheless, we should still take care of drivers that don't behave
1316c167b9c7SMaximilian Luz 	 * well. Thus disable all enabled events, unregister all notifiers.
1317c167b9c7SMaximilian Luz 	 */
1318c167b9c7SMaximilian Luz 	ssam_notifier_unregister_all(ctrl);
1319c167b9c7SMaximilian Luz 
1320c167b9c7SMaximilian Luz 	/*
1321c167b9c7SMaximilian Luz 	 * Cancel remaining requests. Ensure no new ones can be queued and stop
1322c167b9c7SMaximilian Luz 	 * threads.
1323c167b9c7SMaximilian Luz 	 */
1324c167b9c7SMaximilian Luz 	ssh_rtl_shutdown(&ctrl->rtl);
1325c167b9c7SMaximilian Luz 
1326c167b9c7SMaximilian Luz 	/*
1327c167b9c7SMaximilian Luz 	 * Set state via write_once even though we expect to be locked/in an
1328c167b9c7SMaximilian Luz 	 * exclusive context, due to smoke-testing in
1329c167b9c7SMaximilian Luz 	 * ssam_request_sync_submit().
1330c167b9c7SMaximilian Luz 	 */
1331c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STOPPED);
1332c167b9c7SMaximilian Luz 	ctrl->rtl.ptl.serdev = NULL;
1333c167b9c7SMaximilian Luz }
1334c167b9c7SMaximilian Luz 
1335c167b9c7SMaximilian Luz /**
1336c167b9c7SMaximilian Luz  * ssam_controller_destroy() - Destroy the controller and free its resources.
1337c167b9c7SMaximilian Luz  * @ctrl: The controller.
1338c167b9c7SMaximilian Luz  *
1339c167b9c7SMaximilian Luz  * Ensures that all resources associated with the controller get freed. This
1340c167b9c7SMaximilian Luz  * function should only be called after the controller has been stopped via
1341c167b9c7SMaximilian Luz  * ssam_controller_shutdown(). In general, this function should not be called
1342c167b9c7SMaximilian Luz  * directly. The only valid place to call this function directly is during
1343c167b9c7SMaximilian Luz  * initialization, before the controller has been fully initialized and passed
1344c167b9c7SMaximilian Luz  * to other processes. This function is called automatically when the
1345c167b9c7SMaximilian Luz  * reference count of the controller reaches zero.
1346c167b9c7SMaximilian Luz  *
1347c167b9c7SMaximilian Luz  * This function must be called with the main controller lock held (i.e. by
1348c167b9c7SMaximilian Luz  * calling ssam_controller_lock()).
1349c167b9c7SMaximilian Luz  */
ssam_controller_destroy(struct ssam_controller * ctrl)1350c167b9c7SMaximilian Luz void ssam_controller_destroy(struct ssam_controller *ctrl)
1351c167b9c7SMaximilian Luz {
1352c167b9c7SMaximilian Luz 	lockdep_assert_held_write(&ctrl->lock);
1353c167b9c7SMaximilian Luz 
1354c167b9c7SMaximilian Luz 	if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED)
1355c167b9c7SMaximilian Luz 		return;
1356c167b9c7SMaximilian Luz 
1357c167b9c7SMaximilian Luz 	WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED);
1358c167b9c7SMaximilian Luz 
1359c167b9c7SMaximilian Luz 	/*
1360c167b9c7SMaximilian Luz 	 * Note: New events could still have been received after the previous
1361c167b9c7SMaximilian Luz 	 * flush in ssam_controller_shutdown, before the request transport layer
1362c167b9c7SMaximilian Luz 	 * has been shut down. At this point, after the shutdown, we can be sure
1363c167b9c7SMaximilian Luz 	 * that no new events will be queued. The call to ssam_cplt_destroy will
1364c167b9c7SMaximilian Luz 	 * ensure that those remaining are being completed and freed.
1365c167b9c7SMaximilian Luz 	 */
1366c167b9c7SMaximilian Luz 
1367c167b9c7SMaximilian Luz 	/* Actually free resources. */
1368c167b9c7SMaximilian Luz 	ssam_cplt_destroy(&ctrl->cplt);
1369c167b9c7SMaximilian Luz 	ssh_rtl_destroy(&ctrl->rtl);
1370c167b9c7SMaximilian Luz 
1371c167b9c7SMaximilian Luz 	/*
1372c167b9c7SMaximilian Luz 	 * Set state via write_once even though we expect to be locked/in an
1373c167b9c7SMaximilian Luz 	 * exclusive context, due to smoke-testing in
1374c167b9c7SMaximilian Luz 	 * ssam_request_sync_submit().
1375c167b9c7SMaximilian Luz 	 */
1376c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_UNINITIALIZED);
1377c167b9c7SMaximilian Luz }
1378c167b9c7SMaximilian Luz 
1379c167b9c7SMaximilian Luz /**
1380c167b9c7SMaximilian Luz  * ssam_controller_suspend() - Suspend the controller.
1381c167b9c7SMaximilian Luz  * @ctrl: The controller to suspend.
1382c167b9c7SMaximilian Luz  *
1383c167b9c7SMaximilian Luz  * Marks the controller as suspended. Note that display-off and D0-exit
1384c167b9c7SMaximilian Luz  * notifications have to be sent manually before transitioning the controller
1385c167b9c7SMaximilian Luz  * into the suspended state via this function.
1386c167b9c7SMaximilian Luz  *
1387c167b9c7SMaximilian Luz  * See ssam_controller_resume() for the corresponding resume function.
1388c167b9c7SMaximilian Luz  *
1389c167b9c7SMaximilian Luz  * Return: Returns %-EINVAL if the controller is currently not in the
1390c167b9c7SMaximilian Luz  * "started" state.
1391c167b9c7SMaximilian Luz  */
ssam_controller_suspend(struct ssam_controller * ctrl)1392c167b9c7SMaximilian Luz int ssam_controller_suspend(struct ssam_controller *ctrl)
1393c167b9c7SMaximilian Luz {
1394c167b9c7SMaximilian Luz 	ssam_controller_lock(ctrl);
1395c167b9c7SMaximilian Luz 
1396c167b9c7SMaximilian Luz 	if (ctrl->state != SSAM_CONTROLLER_STARTED) {
1397c167b9c7SMaximilian Luz 		ssam_controller_unlock(ctrl);
1398c167b9c7SMaximilian Luz 		return -EINVAL;
1399c167b9c7SMaximilian Luz 	}
1400c167b9c7SMaximilian Luz 
1401c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: suspending controller\n");
1402c167b9c7SMaximilian Luz 
1403c167b9c7SMaximilian Luz 	/*
1404c167b9c7SMaximilian Luz 	 * Set state via write_once even though we're locked, due to
1405c167b9c7SMaximilian Luz 	 * smoke-testing in ssam_request_sync_submit().
1406c167b9c7SMaximilian Luz 	 */
1407c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_SUSPENDED);
1408c167b9c7SMaximilian Luz 
1409c167b9c7SMaximilian Luz 	ssam_controller_unlock(ctrl);
1410c167b9c7SMaximilian Luz 	return 0;
1411c167b9c7SMaximilian Luz }
1412c167b9c7SMaximilian Luz 
1413c167b9c7SMaximilian Luz /**
1414c167b9c7SMaximilian Luz  * ssam_controller_resume() - Resume the controller from suspend.
1415c167b9c7SMaximilian Luz  * @ctrl: The controller to resume.
1416c167b9c7SMaximilian Luz  *
1417c167b9c7SMaximilian Luz  * Resume the controller from the suspended state it was put into via
1418c167b9c7SMaximilian Luz  * ssam_controller_suspend(). This function does not issue display-on and
1419c167b9c7SMaximilian Luz  * D0-entry notifications. If required, those have to be sent manually after
1420c167b9c7SMaximilian Luz  * this call.
1421c167b9c7SMaximilian Luz  *
1422c167b9c7SMaximilian Luz  * Return: Returns %-EINVAL if the controller is currently not suspended.
1423c167b9c7SMaximilian Luz  */
ssam_controller_resume(struct ssam_controller * ctrl)1424c167b9c7SMaximilian Luz int ssam_controller_resume(struct ssam_controller *ctrl)
1425c167b9c7SMaximilian Luz {
1426c167b9c7SMaximilian Luz 	ssam_controller_lock(ctrl);
1427c167b9c7SMaximilian Luz 
1428c167b9c7SMaximilian Luz 	if (ctrl->state != SSAM_CONTROLLER_SUSPENDED) {
1429c167b9c7SMaximilian Luz 		ssam_controller_unlock(ctrl);
1430c167b9c7SMaximilian Luz 		return -EINVAL;
1431c167b9c7SMaximilian Luz 	}
1432c167b9c7SMaximilian Luz 
1433c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: resuming controller\n");
1434c167b9c7SMaximilian Luz 
1435c167b9c7SMaximilian Luz 	/*
1436c167b9c7SMaximilian Luz 	 * Set state via write_once even though we're locked, due to
1437c167b9c7SMaximilian Luz 	 * smoke-testing in ssam_request_sync_submit().
1438c167b9c7SMaximilian Luz 	 */
1439c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED);
1440c167b9c7SMaximilian Luz 
1441c167b9c7SMaximilian Luz 	ssam_controller_unlock(ctrl);
1442c167b9c7SMaximilian Luz 	return 0;
1443c167b9c7SMaximilian Luz }
1444c167b9c7SMaximilian Luz 
1445c167b9c7SMaximilian Luz 
1446c167b9c7SMaximilian Luz /* -- Top-level request interface ------------------------------------------- */
1447c167b9c7SMaximilian Luz 
1448c167b9c7SMaximilian Luz /**
1449c167b9c7SMaximilian Luz  * ssam_request_write_data() - Construct and write SAM request message to
1450c167b9c7SMaximilian Luz  * buffer.
1451c167b9c7SMaximilian Luz  * @buf:  The buffer to write the data to.
1452c167b9c7SMaximilian Luz  * @ctrl: The controller via which the request will be sent.
1453c167b9c7SMaximilian Luz  * @spec: The request data and specification.
1454c167b9c7SMaximilian Luz  *
1455c167b9c7SMaximilian Luz  * Constructs a SAM/SSH request message and writes it to the provided buffer.
1456c167b9c7SMaximilian Luz  * The request and transport counters, specifically RQID and SEQ, will be set
1457c167b9c7SMaximilian Luz  * in this call. These counters are obtained from the controller. It is thus
1458c167b9c7SMaximilian Luz  * only valid to send the resulting message via the controller specified here.
1459c167b9c7SMaximilian Luz  *
1460c167b9c7SMaximilian Luz  * For calculation of the required buffer size, refer to the
1461c167b9c7SMaximilian Luz  * SSH_COMMAND_MESSAGE_LENGTH() macro.
1462c167b9c7SMaximilian Luz  *
1463c167b9c7SMaximilian Luz  * Return: Returns the number of bytes used in the buffer on success. Returns
1464c167b9c7SMaximilian Luz  * %-EINVAL if the payload length provided in the request specification is too
1465c167b9c7SMaximilian Luz  * large (larger than %SSH_COMMAND_MAX_PAYLOAD_SIZE) or if the provided buffer
1466c167b9c7SMaximilian Luz  * is too small.
1467c167b9c7SMaximilian Luz  */
ssam_request_write_data(struct ssam_span * buf,struct ssam_controller * ctrl,const struct ssam_request * spec)1468c167b9c7SMaximilian Luz ssize_t ssam_request_write_data(struct ssam_span *buf,
1469c167b9c7SMaximilian Luz 				struct ssam_controller *ctrl,
1470c167b9c7SMaximilian Luz 				const struct ssam_request *spec)
1471c167b9c7SMaximilian Luz {
1472c167b9c7SMaximilian Luz 	struct msgbuf msgb;
1473c167b9c7SMaximilian Luz 	u16 rqid;
1474c167b9c7SMaximilian Luz 	u8 seq;
1475c167b9c7SMaximilian Luz 
1476c167b9c7SMaximilian Luz 	if (spec->length > SSH_COMMAND_MAX_PAYLOAD_SIZE)
1477c167b9c7SMaximilian Luz 		return -EINVAL;
1478c167b9c7SMaximilian Luz 
1479c167b9c7SMaximilian Luz 	if (SSH_COMMAND_MESSAGE_LENGTH(spec->length) > buf->len)
1480c167b9c7SMaximilian Luz 		return -EINVAL;
1481c167b9c7SMaximilian Luz 
1482c167b9c7SMaximilian Luz 	msgb_init(&msgb, buf->ptr, buf->len);
1483c167b9c7SMaximilian Luz 	seq = ssh_seq_next(&ctrl->counter.seq);
1484c167b9c7SMaximilian Luz 	rqid = ssh_rqid_next(&ctrl->counter.rqid);
1485c167b9c7SMaximilian Luz 	msgb_push_cmd(&msgb, seq, rqid, spec);
1486c167b9c7SMaximilian Luz 
1487c167b9c7SMaximilian Luz 	return msgb_bytes_used(&msgb);
1488c167b9c7SMaximilian Luz }
1489c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_write_data);
1490c167b9c7SMaximilian Luz 
ssam_request_sync_complete(struct ssh_request * rqst,const struct ssh_command * cmd,const struct ssam_span * data,int status)1491c167b9c7SMaximilian Luz static void ssam_request_sync_complete(struct ssh_request *rqst,
1492c167b9c7SMaximilian Luz 				       const struct ssh_command *cmd,
1493c167b9c7SMaximilian Luz 				       const struct ssam_span *data, int status)
1494c167b9c7SMaximilian Luz {
1495c167b9c7SMaximilian Luz 	struct ssh_rtl *rtl = ssh_request_rtl(rqst);
1496c167b9c7SMaximilian Luz 	struct ssam_request_sync *r;
1497c167b9c7SMaximilian Luz 
1498c167b9c7SMaximilian Luz 	r = container_of(rqst, struct ssam_request_sync, base);
1499c167b9c7SMaximilian Luz 	r->status = status;
1500c167b9c7SMaximilian Luz 
1501c167b9c7SMaximilian Luz 	if (r->resp)
1502c167b9c7SMaximilian Luz 		r->resp->length = 0;
1503c167b9c7SMaximilian Luz 
1504c167b9c7SMaximilian Luz 	if (status) {
1505c167b9c7SMaximilian Luz 		rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status);
1506c167b9c7SMaximilian Luz 		return;
1507c167b9c7SMaximilian Luz 	}
1508c167b9c7SMaximilian Luz 
1509c167b9c7SMaximilian Luz 	if (!data)	/* Handle requests without a response. */
1510c167b9c7SMaximilian Luz 		return;
1511c167b9c7SMaximilian Luz 
1512c167b9c7SMaximilian Luz 	if (!r->resp || !r->resp->pointer) {
1513c167b9c7SMaximilian Luz 		if (data->len)
1514c167b9c7SMaximilian Luz 			rtl_warn(rtl, "rsp: no response buffer provided, dropping data\n");
1515c167b9c7SMaximilian Luz 		return;
1516c167b9c7SMaximilian Luz 	}
1517c167b9c7SMaximilian Luz 
1518c167b9c7SMaximilian Luz 	if (data->len > r->resp->capacity) {
1519c167b9c7SMaximilian Luz 		rtl_err(rtl,
1520c167b9c7SMaximilian Luz 			"rsp: response buffer too small, capacity: %zu bytes, got: %zu bytes\n",
1521c167b9c7SMaximilian Luz 			r->resp->capacity, data->len);
1522c167b9c7SMaximilian Luz 		r->status = -ENOSPC;
1523c167b9c7SMaximilian Luz 		return;
1524c167b9c7SMaximilian Luz 	}
1525c167b9c7SMaximilian Luz 
1526c167b9c7SMaximilian Luz 	r->resp->length = data->len;
1527c167b9c7SMaximilian Luz 	memcpy(r->resp->pointer, data->ptr, data->len);
1528c167b9c7SMaximilian Luz }
1529c167b9c7SMaximilian Luz 
ssam_request_sync_release(struct ssh_request * rqst)1530c167b9c7SMaximilian Luz static void ssam_request_sync_release(struct ssh_request *rqst)
1531c167b9c7SMaximilian Luz {
1532c167b9c7SMaximilian Luz 	complete_all(&container_of(rqst, struct ssam_request_sync, base)->comp);
1533c167b9c7SMaximilian Luz }
1534c167b9c7SMaximilian Luz 
1535c167b9c7SMaximilian Luz static const struct ssh_request_ops ssam_request_sync_ops = {
1536c167b9c7SMaximilian Luz 	.release = ssam_request_sync_release,
1537c167b9c7SMaximilian Luz 	.complete = ssam_request_sync_complete,
1538c167b9c7SMaximilian Luz };
1539c167b9c7SMaximilian Luz 
1540c167b9c7SMaximilian Luz /**
1541c167b9c7SMaximilian Luz  * ssam_request_sync_alloc() - Allocate a synchronous request.
1542c167b9c7SMaximilian Luz  * @payload_len: The length of the request payload.
1543c167b9c7SMaximilian Luz  * @flags:       Flags used for allocation.
1544c167b9c7SMaximilian Luz  * @rqst:        Where to store the pointer to the allocated request.
1545c167b9c7SMaximilian Luz  * @buffer:      Where to store the buffer descriptor for the message buffer of
1546c167b9c7SMaximilian Luz  *               the request.
1547c167b9c7SMaximilian Luz  *
1548c167b9c7SMaximilian Luz  * Allocates a synchronous request with corresponding message buffer. The
1549c167b9c7SMaximilian Luz  * request still needs to be initialized ssam_request_sync_init() before
1550c167b9c7SMaximilian Luz  * it can be submitted, and the message buffer data must still be set to the
1551c167b9c7SMaximilian Luz  * returned buffer via ssam_request_sync_set_data() after it has been filled,
1552c167b9c7SMaximilian Luz  * if need be with adjusted message length.
1553c167b9c7SMaximilian Luz  *
1554c167b9c7SMaximilian Luz  * After use, the request and its corresponding message buffer should be freed
1555c167b9c7SMaximilian Luz  * via ssam_request_sync_free(). The buffer must not be freed separately.
1556c167b9c7SMaximilian Luz  *
1557c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-ENOMEM if the request could not be
1558c167b9c7SMaximilian Luz  * allocated.
1559c167b9c7SMaximilian Luz  */
ssam_request_sync_alloc(size_t payload_len,gfp_t flags,struct ssam_request_sync ** rqst,struct ssam_span * buffer)1560c167b9c7SMaximilian Luz int ssam_request_sync_alloc(size_t payload_len, gfp_t flags,
1561c167b9c7SMaximilian Luz 			    struct ssam_request_sync **rqst,
1562c167b9c7SMaximilian Luz 			    struct ssam_span *buffer)
1563c167b9c7SMaximilian Luz {
1564c167b9c7SMaximilian Luz 	size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len);
1565c167b9c7SMaximilian Luz 
1566c167b9c7SMaximilian Luz 	*rqst = kzalloc(sizeof(**rqst) + msglen, flags);
1567c167b9c7SMaximilian Luz 	if (!*rqst)
1568c167b9c7SMaximilian Luz 		return -ENOMEM;
1569c167b9c7SMaximilian Luz 
1570c167b9c7SMaximilian Luz 	buffer->ptr = (u8 *)(*rqst + 1);
1571c167b9c7SMaximilian Luz 	buffer->len = msglen;
1572c167b9c7SMaximilian Luz 
1573c167b9c7SMaximilian Luz 	return 0;
1574c167b9c7SMaximilian Luz }
1575c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_alloc);
1576c167b9c7SMaximilian Luz 
1577c167b9c7SMaximilian Luz /**
1578c167b9c7SMaximilian Luz  * ssam_request_sync_free() - Free a synchronous request.
1579c167b9c7SMaximilian Luz  * @rqst: The request to be freed.
1580c167b9c7SMaximilian Luz  *
1581c167b9c7SMaximilian Luz  * Free a synchronous request and its corresponding buffer allocated with
1582c167b9c7SMaximilian Luz  * ssam_request_sync_alloc(). Do not use for requests allocated on the stack
1583c167b9c7SMaximilian Luz  * or via any other function.
1584c167b9c7SMaximilian Luz  *
1585c167b9c7SMaximilian Luz  * Warning: The caller must ensure that the request is not in use any more.
1586c167b9c7SMaximilian Luz  * I.e. the caller must ensure that it has the only reference to the request
1587c167b9c7SMaximilian Luz  * and the request is not currently pending. This means that the caller has
1588c167b9c7SMaximilian Luz  * either never submitted the request, request submission has failed, or the
1589c167b9c7SMaximilian Luz  * caller has waited until the submitted request has been completed via
1590c167b9c7SMaximilian Luz  * ssam_request_sync_wait().
1591c167b9c7SMaximilian Luz  */
ssam_request_sync_free(struct ssam_request_sync * rqst)1592c167b9c7SMaximilian Luz void ssam_request_sync_free(struct ssam_request_sync *rqst)
1593c167b9c7SMaximilian Luz {
1594c167b9c7SMaximilian Luz 	kfree(rqst);
1595c167b9c7SMaximilian Luz }
1596c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_free);
1597c167b9c7SMaximilian Luz 
1598c167b9c7SMaximilian Luz /**
1599c167b9c7SMaximilian Luz  * ssam_request_sync_init() - Initialize a synchronous request struct.
1600c167b9c7SMaximilian Luz  * @rqst:  The request to initialize.
1601c167b9c7SMaximilian Luz  * @flags: The request flags.
1602c167b9c7SMaximilian Luz  *
1603c167b9c7SMaximilian Luz  * Initializes the given request struct. Does not initialize the request
1604c167b9c7SMaximilian Luz  * message data. This has to be done explicitly after this call via
1605c167b9c7SMaximilian Luz  * ssam_request_sync_set_data() and the actual message data has to be written
1606c167b9c7SMaximilian Luz  * via ssam_request_write_data().
1607c167b9c7SMaximilian Luz  *
1608c167b9c7SMaximilian Luz  * Return: Returns zero on success or %-EINVAL if the given flags are invalid.
1609c167b9c7SMaximilian Luz  */
ssam_request_sync_init(struct ssam_request_sync * rqst,enum ssam_request_flags flags)1610c167b9c7SMaximilian Luz int ssam_request_sync_init(struct ssam_request_sync *rqst,
1611c167b9c7SMaximilian Luz 			   enum ssam_request_flags flags)
1612c167b9c7SMaximilian Luz {
1613c167b9c7SMaximilian Luz 	int status;
1614c167b9c7SMaximilian Luz 
1615c167b9c7SMaximilian Luz 	status = ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops);
1616c167b9c7SMaximilian Luz 	if (status)
1617c167b9c7SMaximilian Luz 		return status;
1618c167b9c7SMaximilian Luz 
1619c167b9c7SMaximilian Luz 	init_completion(&rqst->comp);
1620c167b9c7SMaximilian Luz 	rqst->resp = NULL;
1621c167b9c7SMaximilian Luz 	rqst->status = 0;
1622c167b9c7SMaximilian Luz 
1623c167b9c7SMaximilian Luz 	return 0;
1624c167b9c7SMaximilian Luz }
1625c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_init);
1626c167b9c7SMaximilian Luz 
1627c167b9c7SMaximilian Luz /**
1628c167b9c7SMaximilian Luz  * ssam_request_sync_submit() - Submit a synchronous request.
1629c167b9c7SMaximilian Luz  * @ctrl: The controller with which to submit the request.
1630c167b9c7SMaximilian Luz  * @rqst: The request to submit.
1631c167b9c7SMaximilian Luz  *
1632c167b9c7SMaximilian Luz  * Submit a synchronous request. The request has to be initialized and
1633c167b9c7SMaximilian Luz  * properly set up, including response buffer (may be %NULL if no response is
1634c167b9c7SMaximilian Luz  * expected) and command message data. This function does not wait for the
1635c167b9c7SMaximilian Luz  * request to be completed.
1636c167b9c7SMaximilian Luz  *
1637c167b9c7SMaximilian Luz  * If this function succeeds, ssam_request_sync_wait() must be used to ensure
1638c167b9c7SMaximilian Luz  * that the request has been completed before the response data can be
1639c167b9c7SMaximilian Luz  * accessed and/or the request can be freed. On failure, the request may
1640c167b9c7SMaximilian Luz  * immediately be freed.
1641c167b9c7SMaximilian Luz  *
1642c167b9c7SMaximilian Luz  * This function may only be used if the controller is active, i.e. has been
1643c167b9c7SMaximilian Luz  * initialized and not suspended.
1644c167b9c7SMaximilian Luz  */
ssam_request_sync_submit(struct ssam_controller * ctrl,struct ssam_request_sync * rqst)1645c167b9c7SMaximilian Luz int ssam_request_sync_submit(struct ssam_controller *ctrl,
1646c167b9c7SMaximilian Luz 			     struct ssam_request_sync *rqst)
1647c167b9c7SMaximilian Luz {
1648c167b9c7SMaximilian Luz 	int status;
1649c167b9c7SMaximilian Luz 
1650c167b9c7SMaximilian Luz 	/*
1651c167b9c7SMaximilian Luz 	 * This is only a superficial check. In general, the caller needs to
1652c167b9c7SMaximilian Luz 	 * ensure that the controller is initialized and is not (and does not
1653c167b9c7SMaximilian Luz 	 * get) suspended during use, i.e. until the request has been completed
1654c167b9c7SMaximilian Luz 	 * (if _absolutely_ necessary, by use of ssam_controller_statelock/
1655c167b9c7SMaximilian Luz 	 * ssam_controller_stateunlock, but something like ssam_client_link
1656c167b9c7SMaximilian Luz 	 * should be preferred as this needs to last until the request has been
1657c167b9c7SMaximilian Luz 	 * completed).
1658c167b9c7SMaximilian Luz 	 *
1659c167b9c7SMaximilian Luz 	 * Note that it is actually safe to use this function while the
1660c167b9c7SMaximilian Luz 	 * controller is in the process of being shut down (as ssh_rtl_submit
1661c167b9c7SMaximilian Luz 	 * is safe with regards to this), but it is generally discouraged to do
1662c167b9c7SMaximilian Luz 	 * so.
1663c167b9c7SMaximilian Luz 	 */
1664c167b9c7SMaximilian Luz 	if (WARN_ON(READ_ONCE(ctrl->state) != SSAM_CONTROLLER_STARTED)) {
1665c167b9c7SMaximilian Luz 		ssh_request_put(&rqst->base);
1666c167b9c7SMaximilian Luz 		return -ENODEV;
1667c167b9c7SMaximilian Luz 	}
1668c167b9c7SMaximilian Luz 
1669c167b9c7SMaximilian Luz 	status = ssh_rtl_submit(&ctrl->rtl, &rqst->base);
1670c167b9c7SMaximilian Luz 	ssh_request_put(&rqst->base);
1671c167b9c7SMaximilian Luz 
1672c167b9c7SMaximilian Luz 	return status;
1673c167b9c7SMaximilian Luz }
1674c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_submit);
1675c167b9c7SMaximilian Luz 
1676c167b9c7SMaximilian Luz /**
1677b09ee1cdSMaximilian Luz  * ssam_request_do_sync() - Execute a synchronous request.
1678c167b9c7SMaximilian Luz  * @ctrl: The controller via which the request will be submitted.
1679c167b9c7SMaximilian Luz  * @spec: The request specification and payload.
1680c167b9c7SMaximilian Luz  * @rsp:  The response buffer.
1681c167b9c7SMaximilian Luz  *
1682c167b9c7SMaximilian Luz  * Allocates a synchronous request with its message data buffer on the heap
1683c167b9c7SMaximilian Luz  * via ssam_request_sync_alloc(), fully initializes it via the provided
1684c167b9c7SMaximilian Luz  * request specification, submits it, and finally waits for its completion
1685c167b9c7SMaximilian Luz  * before freeing it and returning its status.
1686c167b9c7SMaximilian Luz  *
1687c167b9c7SMaximilian Luz  * Return: Returns the status of the request or any failure during setup.
1688c167b9c7SMaximilian Luz  */
ssam_request_do_sync(struct ssam_controller * ctrl,const struct ssam_request * spec,struct ssam_response * rsp)1689b09ee1cdSMaximilian Luz int ssam_request_do_sync(struct ssam_controller *ctrl,
1690c167b9c7SMaximilian Luz 			 const struct ssam_request *spec,
1691c167b9c7SMaximilian Luz 			 struct ssam_response *rsp)
1692c167b9c7SMaximilian Luz {
1693c167b9c7SMaximilian Luz 	struct ssam_request_sync *rqst;
1694c167b9c7SMaximilian Luz 	struct ssam_span buf;
1695c167b9c7SMaximilian Luz 	ssize_t len;
1696c167b9c7SMaximilian Luz 	int status;
1697c167b9c7SMaximilian Luz 
1698c167b9c7SMaximilian Luz 	status = ssam_request_sync_alloc(spec->length, GFP_KERNEL, &rqst, &buf);
1699c167b9c7SMaximilian Luz 	if (status)
1700c167b9c7SMaximilian Luz 		return status;
1701c167b9c7SMaximilian Luz 
1702c167b9c7SMaximilian Luz 	status = ssam_request_sync_init(rqst, spec->flags);
1703c965daacSMaximilian Luz 	if (status) {
1704c965daacSMaximilian Luz 		ssam_request_sync_free(rqst);
1705c167b9c7SMaximilian Luz 		return status;
1706c965daacSMaximilian Luz 	}
1707c167b9c7SMaximilian Luz 
1708c167b9c7SMaximilian Luz 	ssam_request_sync_set_resp(rqst, rsp);
1709c167b9c7SMaximilian Luz 
1710c167b9c7SMaximilian Luz 	len = ssam_request_write_data(&buf, ctrl, spec);
1711c167b9c7SMaximilian Luz 	if (len < 0) {
1712c167b9c7SMaximilian Luz 		ssam_request_sync_free(rqst);
1713c167b9c7SMaximilian Luz 		return len;
1714c167b9c7SMaximilian Luz 	}
1715c167b9c7SMaximilian Luz 
1716c167b9c7SMaximilian Luz 	ssam_request_sync_set_data(rqst, buf.ptr, len);
1717c167b9c7SMaximilian Luz 
1718c167b9c7SMaximilian Luz 	status = ssam_request_sync_submit(ctrl, rqst);
1719c167b9c7SMaximilian Luz 	if (!status)
1720c167b9c7SMaximilian Luz 		status = ssam_request_sync_wait(rqst);
1721c167b9c7SMaximilian Luz 
1722c167b9c7SMaximilian Luz 	ssam_request_sync_free(rqst);
1723c167b9c7SMaximilian Luz 	return status;
1724c167b9c7SMaximilian Luz }
1725b09ee1cdSMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_do_sync);
1726c167b9c7SMaximilian Luz 
1727c167b9c7SMaximilian Luz /**
1728b09ee1cdSMaximilian Luz  * ssam_request_do_sync_with_buffer() - Execute a synchronous request with the
1729c167b9c7SMaximilian Luz  * provided buffer as back-end for the message buffer.
1730c167b9c7SMaximilian Luz  * @ctrl: The controller via which the request will be submitted.
1731c167b9c7SMaximilian Luz  * @spec: The request specification and payload.
1732c167b9c7SMaximilian Luz  * @rsp:  The response buffer.
1733c167b9c7SMaximilian Luz  * @buf:  The buffer for the request message data.
1734c167b9c7SMaximilian Luz  *
1735c167b9c7SMaximilian Luz  * Allocates a synchronous request struct on the stack, fully initializes it
1736c167b9c7SMaximilian Luz  * using the provided buffer as message data buffer, submits it, and then
1737c167b9c7SMaximilian Luz  * waits for its completion before returning its status. The
1738c167b9c7SMaximilian Luz  * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required
1739c167b9c7SMaximilian Luz  * message buffer size.
1740c167b9c7SMaximilian Luz  *
1741b09ee1cdSMaximilian Luz  * This function does essentially the same as ssam_request_do_sync(), but
1742b09ee1cdSMaximilian Luz  * instead of dynamically allocating the request and message data buffer, it
1743b09ee1cdSMaximilian Luz  * uses the provided message data buffer and stores the (small) request struct
1744b09ee1cdSMaximilian Luz  * on the heap.
1745c167b9c7SMaximilian Luz  *
1746c167b9c7SMaximilian Luz  * Return: Returns the status of the request or any failure during setup.
1747c167b9c7SMaximilian Luz  */
ssam_request_do_sync_with_buffer(struct ssam_controller * ctrl,const struct ssam_request * spec,struct ssam_response * rsp,struct ssam_span * buf)1748b09ee1cdSMaximilian Luz int ssam_request_do_sync_with_buffer(struct ssam_controller *ctrl,
1749c167b9c7SMaximilian Luz 				     const struct ssam_request *spec,
1750c167b9c7SMaximilian Luz 				     struct ssam_response *rsp,
1751c167b9c7SMaximilian Luz 				     struct ssam_span *buf)
1752c167b9c7SMaximilian Luz {
1753c167b9c7SMaximilian Luz 	struct ssam_request_sync rqst;
1754c167b9c7SMaximilian Luz 	ssize_t len;
1755c167b9c7SMaximilian Luz 	int status;
1756c167b9c7SMaximilian Luz 
1757c167b9c7SMaximilian Luz 	status = ssam_request_sync_init(&rqst, spec->flags);
1758c167b9c7SMaximilian Luz 	if (status)
1759c167b9c7SMaximilian Luz 		return status;
1760c167b9c7SMaximilian Luz 
1761c167b9c7SMaximilian Luz 	ssam_request_sync_set_resp(&rqst, rsp);
1762c167b9c7SMaximilian Luz 
1763c167b9c7SMaximilian Luz 	len = ssam_request_write_data(buf, ctrl, spec);
1764c167b9c7SMaximilian Luz 	if (len < 0)
1765c167b9c7SMaximilian Luz 		return len;
1766c167b9c7SMaximilian Luz 
1767c167b9c7SMaximilian Luz 	ssam_request_sync_set_data(&rqst, buf->ptr, len);
1768c167b9c7SMaximilian Luz 
1769c167b9c7SMaximilian Luz 	status = ssam_request_sync_submit(ctrl, &rqst);
1770c167b9c7SMaximilian Luz 	if (!status)
1771c167b9c7SMaximilian Luz 		status = ssam_request_sync_wait(&rqst);
1772c167b9c7SMaximilian Luz 
1773c167b9c7SMaximilian Luz 	return status;
1774c167b9c7SMaximilian Luz }
1775b09ee1cdSMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_do_sync_with_buffer);
1776c167b9c7SMaximilian Luz 
1777c167b9c7SMaximilian Luz 
1778c167b9c7SMaximilian Luz /* -- Internal SAM requests. ------------------------------------------------ */
1779c167b9c7SMaximilian Luz 
178003ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
1781c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
17823f88b459SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1783c167b9c7SMaximilian Luz 	.command_id      = 0x13,
1784c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1785c167b9c7SMaximilian Luz });
1786c167b9c7SMaximilian Luz 
178703ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
1788c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
17893f88b459SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1790c167b9c7SMaximilian Luz 	.command_id      = 0x15,
1791c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1792c167b9c7SMaximilian Luz });
1793c167b9c7SMaximilian Luz 
179403ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
1795c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
17963f88b459SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1797c167b9c7SMaximilian Luz 	.command_id      = 0x16,
1798c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1799c167b9c7SMaximilian Luz });
1800c167b9c7SMaximilian Luz 
180103ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
1802c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
18033f88b459SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1804c167b9c7SMaximilian Luz 	.command_id      = 0x33,
1805c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1806c167b9c7SMaximilian Luz });
1807c167b9c7SMaximilian Luz 
180803ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
1809c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
18103f88b459SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1811c167b9c7SMaximilian Luz 	.command_id      = 0x34,
1812c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1813c167b9c7SMaximilian Luz });
1814c167b9c7SMaximilian Luz 
1815c167b9c7SMaximilian Luz /**
1816c167b9c7SMaximilian Luz  * struct ssh_notification_params - Command payload to enable/disable SSH
1817c167b9c7SMaximilian Luz  * notifications.
1818c167b9c7SMaximilian Luz  * @target_category: The target category for which notifications should be
1819c167b9c7SMaximilian Luz  *                   enabled/disabled.
1820c167b9c7SMaximilian Luz  * @flags:           Flags determining how notifications are being sent.
1821c167b9c7SMaximilian Luz  * @request_id:      The request ID that is used to send these notifications.
1822c167b9c7SMaximilian Luz  * @instance_id:     The specific instance in the given target category for
1823c167b9c7SMaximilian Luz  *                   which notifications should be enabled.
1824c167b9c7SMaximilian Luz  */
1825c167b9c7SMaximilian Luz struct ssh_notification_params {
1826c167b9c7SMaximilian Luz 	u8 target_category;
1827c167b9c7SMaximilian Luz 	u8 flags;
1828c167b9c7SMaximilian Luz 	__le16 request_id;
1829c167b9c7SMaximilian Luz 	u8 instance_id;
1830c167b9c7SMaximilian Luz } __packed;
1831c167b9c7SMaximilian Luz 
1832c167b9c7SMaximilian Luz static_assert(sizeof(struct ssh_notification_params) == 5);
1833c167b9c7SMaximilian Luz 
__ssam_ssh_event_request(struct ssam_controller * ctrl,struct ssam_event_registry reg,u8 cid,struct ssam_event_id id,u8 flags)1834c167b9c7SMaximilian Luz static int __ssam_ssh_event_request(struct ssam_controller *ctrl,
1835c167b9c7SMaximilian Luz 				    struct ssam_event_registry reg, u8 cid,
1836c167b9c7SMaximilian Luz 				    struct ssam_event_id id, u8 flags)
1837c167b9c7SMaximilian Luz {
1838c167b9c7SMaximilian Luz 	struct ssh_notification_params params;
1839c167b9c7SMaximilian Luz 	struct ssam_request rqst;
1840c167b9c7SMaximilian Luz 	struct ssam_response result;
1841c167b9c7SMaximilian Luz 	int status;
1842c167b9c7SMaximilian Luz 
1843c167b9c7SMaximilian Luz 	u16 rqid = ssh_tc_to_rqid(id.target_category);
1844c167b9c7SMaximilian Luz 	u8 buf = 0;
1845c167b9c7SMaximilian Luz 
1846c167b9c7SMaximilian Luz 	/* Only allow RQIDs that lie within the event spectrum. */
1847c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid))
1848c167b9c7SMaximilian Luz 		return -EINVAL;
1849c167b9c7SMaximilian Luz 
1850c167b9c7SMaximilian Luz 	params.target_category = id.target_category;
1851c167b9c7SMaximilian Luz 	params.instance_id = id.instance;
1852c167b9c7SMaximilian Luz 	params.flags = flags;
1853c167b9c7SMaximilian Luz 	put_unaligned_le16(rqid, &params.request_id);
1854c167b9c7SMaximilian Luz 
1855c167b9c7SMaximilian Luz 	rqst.target_category = reg.target_category;
1856c167b9c7SMaximilian Luz 	rqst.target_id = reg.target_id;
1857c167b9c7SMaximilian Luz 	rqst.command_id = cid;
1858c167b9c7SMaximilian Luz 	rqst.instance_id = 0x00;
1859c167b9c7SMaximilian Luz 	rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
1860c167b9c7SMaximilian Luz 	rqst.length = sizeof(params);
1861c167b9c7SMaximilian Luz 	rqst.payload = (u8 *)&params;
1862c167b9c7SMaximilian Luz 
1863c167b9c7SMaximilian Luz 	result.capacity = sizeof(buf);
1864c167b9c7SMaximilian Luz 	result.length = 0;
1865c167b9c7SMaximilian Luz 	result.pointer = &buf;
1866c167b9c7SMaximilian Luz 
1867b09ee1cdSMaximilian Luz 	status = ssam_retry(ssam_request_do_sync_onstack, ctrl, &rqst, &result,
1868c167b9c7SMaximilian Luz 			    sizeof(params));
1869c167b9c7SMaximilian Luz 
1870c167b9c7SMaximilian Luz 	return status < 0 ? status : buf;
1871c167b9c7SMaximilian Luz }
1872c167b9c7SMaximilian Luz 
1873c167b9c7SMaximilian Luz /**
1874c167b9c7SMaximilian Luz  * ssam_ssh_event_enable() - Enable SSH event.
1875c167b9c7SMaximilian Luz  * @ctrl:  The controller for which to enable the event.
1876c167b9c7SMaximilian Luz  * @reg:   The event registry describing what request to use for enabling and
1877c167b9c7SMaximilian Luz  *         disabling the event.
1878c167b9c7SMaximilian Luz  * @id:    The event identifier.
1879c167b9c7SMaximilian Luz  * @flags: The event flags.
1880c167b9c7SMaximilian Luz  *
1881c167b9c7SMaximilian Luz  * Enables the specified event on the EC. This function does not manage
1882c167b9c7SMaximilian Luz  * reference counting of enabled events and is basically only a wrapper for
1883c167b9c7SMaximilian Luz  * the raw EC request. If the specified event is already enabled, the EC will
1884c167b9c7SMaximilian Luz  * ignore this request.
1885c167b9c7SMaximilian Luz  *
1886c167b9c7SMaximilian Luz  * Return: Returns the status of the executed SAM request (zero on success and
1887c167b9c7SMaximilian Luz  * negative on direct failure) or %-EPROTO if the request response indicates a
1888c167b9c7SMaximilian Luz  * failure.
1889c167b9c7SMaximilian Luz  */
ssam_ssh_event_enable(struct ssam_controller * ctrl,struct ssam_event_registry reg,struct ssam_event_id id,u8 flags)1890c167b9c7SMaximilian Luz static int ssam_ssh_event_enable(struct ssam_controller *ctrl,
1891c167b9c7SMaximilian Luz 				 struct ssam_event_registry reg,
1892c167b9c7SMaximilian Luz 				 struct ssam_event_id id, u8 flags)
1893c167b9c7SMaximilian Luz {
1894c167b9c7SMaximilian Luz 	int status;
1895c167b9c7SMaximilian Luz 
1896c167b9c7SMaximilian Luz 	status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags);
1897c167b9c7SMaximilian Luz 
1898c167b9c7SMaximilian Luz 	if (status < 0 && status != -EINVAL) {
1899c167b9c7SMaximilian Luz 		ssam_err(ctrl,
1900c167b9c7SMaximilian Luz 			 "failed to enable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n",
1901c167b9c7SMaximilian Luz 			 id.target_category, id.instance, reg.target_category);
1902c167b9c7SMaximilian Luz 	}
1903c167b9c7SMaximilian Luz 
1904c167b9c7SMaximilian Luz 	if (status > 0) {
1905c167b9c7SMaximilian Luz 		ssam_err(ctrl,
1906c167b9c7SMaximilian Luz 			 "unexpected result while enabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n",
1907c167b9c7SMaximilian Luz 			 status, id.target_category, id.instance, reg.target_category);
1908c167b9c7SMaximilian Luz 		return -EPROTO;
1909c167b9c7SMaximilian Luz 	}
1910c167b9c7SMaximilian Luz 
1911c167b9c7SMaximilian Luz 	return status;
1912c167b9c7SMaximilian Luz }
1913c167b9c7SMaximilian Luz 
1914c167b9c7SMaximilian Luz /**
1915c167b9c7SMaximilian Luz  * ssam_ssh_event_disable() - Disable SSH event.
1916c167b9c7SMaximilian Luz  * @ctrl:  The controller for which to disable the event.
1917c167b9c7SMaximilian Luz  * @reg:   The event registry describing what request to use for enabling and
1918c167b9c7SMaximilian Luz  *         disabling the event (must be same as used when enabling the event).
1919c167b9c7SMaximilian Luz  * @id:    The event identifier.
1920c167b9c7SMaximilian Luz  * @flags: The event flags (likely ignored for disabling of events).
1921c167b9c7SMaximilian Luz  *
1922c167b9c7SMaximilian Luz  * Disables the specified event on the EC. This function does not manage
1923c167b9c7SMaximilian Luz  * reference counting of enabled events and is basically only a wrapper for
1924c167b9c7SMaximilian Luz  * the raw EC request. If the specified event is already disabled, the EC will
1925c167b9c7SMaximilian Luz  * ignore this request.
1926c167b9c7SMaximilian Luz  *
1927c167b9c7SMaximilian Luz  * Return: Returns the status of the executed SAM request (zero on success and
1928c167b9c7SMaximilian Luz  * negative on direct failure) or %-EPROTO if the request response indicates a
1929c167b9c7SMaximilian Luz  * failure.
1930c167b9c7SMaximilian Luz  */
ssam_ssh_event_disable(struct ssam_controller * ctrl,struct ssam_event_registry reg,struct ssam_event_id id,u8 flags)1931c167b9c7SMaximilian Luz static int ssam_ssh_event_disable(struct ssam_controller *ctrl,
1932c167b9c7SMaximilian Luz 				  struct ssam_event_registry reg,
1933c167b9c7SMaximilian Luz 				  struct ssam_event_id id, u8 flags)
1934c167b9c7SMaximilian Luz {
1935c167b9c7SMaximilian Luz 	int status;
1936c167b9c7SMaximilian Luz 
19376cbaee2eSMaximilian Luz 	status = __ssam_ssh_event_request(ctrl, reg, reg.cid_disable, id, flags);
1938c167b9c7SMaximilian Luz 
1939c167b9c7SMaximilian Luz 	if (status < 0 && status != -EINVAL) {
1940c167b9c7SMaximilian Luz 		ssam_err(ctrl,
1941c167b9c7SMaximilian Luz 			 "failed to disable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n",
1942c167b9c7SMaximilian Luz 			 id.target_category, id.instance, reg.target_category);
1943c167b9c7SMaximilian Luz 	}
1944c167b9c7SMaximilian Luz 
1945c167b9c7SMaximilian Luz 	if (status > 0) {
1946c167b9c7SMaximilian Luz 		ssam_err(ctrl,
1947c167b9c7SMaximilian Luz 			 "unexpected result while disabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n",
1948c167b9c7SMaximilian Luz 			 status, id.target_category, id.instance, reg.target_category);
1949c167b9c7SMaximilian Luz 		return -EPROTO;
1950c167b9c7SMaximilian Luz 	}
1951c167b9c7SMaximilian Luz 
1952c167b9c7SMaximilian Luz 	return status;
1953c167b9c7SMaximilian Luz }
1954c167b9c7SMaximilian Luz 
1955c167b9c7SMaximilian Luz 
1956c167b9c7SMaximilian Luz /* -- Wrappers for internal SAM requests. ----------------------------------- */
1957c167b9c7SMaximilian Luz 
1958c167b9c7SMaximilian Luz /**
1959c167b9c7SMaximilian Luz  * ssam_get_firmware_version() - Get the SAM/EC firmware version.
1960c167b9c7SMaximilian Luz  * @ctrl:    The controller.
1961c167b9c7SMaximilian Luz  * @version: Where to store the version number.
1962c167b9c7SMaximilian Luz  *
1963c167b9c7SMaximilian Luz  * Return: Returns zero on success or the status of the executed SAM request
1964c167b9c7SMaximilian Luz  * if that request failed.
1965c167b9c7SMaximilian Luz  */
ssam_get_firmware_version(struct ssam_controller * ctrl,u32 * version)1966c167b9c7SMaximilian Luz int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version)
1967c167b9c7SMaximilian Luz {
1968c167b9c7SMaximilian Luz 	__le32 __version;
1969c167b9c7SMaximilian Luz 	int status;
1970c167b9c7SMaximilian Luz 
1971c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_get_firmware_version, ctrl, &__version);
1972c167b9c7SMaximilian Luz 	if (status)
1973c167b9c7SMaximilian Luz 		return status;
1974c167b9c7SMaximilian Luz 
1975c167b9c7SMaximilian Luz 	*version = le32_to_cpu(__version);
1976c167b9c7SMaximilian Luz 	return 0;
1977c167b9c7SMaximilian Luz }
1978c167b9c7SMaximilian Luz 
1979c167b9c7SMaximilian Luz /**
1980c167b9c7SMaximilian Luz  * ssam_ctrl_notif_display_off() - Notify EC that the display has been turned
1981c167b9c7SMaximilian Luz  * off.
1982c167b9c7SMaximilian Luz  * @ctrl: The controller.
1983c167b9c7SMaximilian Luz  *
1984c167b9c7SMaximilian Luz  * Notify the EC that the display has been turned off and the driver may enter
1985c167b9c7SMaximilian Luz  * a lower-power state. This will prevent events from being sent directly.
1986c167b9c7SMaximilian Luz  * Rather, the EC signals an event by pulling the wakeup GPIO high for as long
1987c167b9c7SMaximilian Luz  * as there are pending events. The events then need to be manually released,
1988c167b9c7SMaximilian Luz  * one by one, via the GPIO callback request. All pending events accumulated
1989c167b9c7SMaximilian Luz  * during this state can also be released by issuing the display-on
1990c167b9c7SMaximilian Luz  * notification, e.g. via ssam_ctrl_notif_display_on(), which will also reset
1991c167b9c7SMaximilian Luz  * the GPIO.
1992c167b9c7SMaximilian Luz  *
1993c167b9c7SMaximilian Luz  * On some devices, specifically ones with an integrated keyboard, the keyboard
1994c167b9c7SMaximilian Luz  * backlight will be turned off by this call.
1995c167b9c7SMaximilian Luz  *
1996c167b9c7SMaximilian Luz  * This function will only send the display-off notification command if
1997c167b9c7SMaximilian Luz  * display notifications are supported by the EC. Currently all known devices
1998c167b9c7SMaximilian Luz  * support these notifications.
1999c167b9c7SMaximilian Luz  *
2000c167b9c7SMaximilian Luz  * Use ssam_ctrl_notif_display_on() to reverse the effects of this function.
2001c167b9c7SMaximilian Luz  *
2002c167b9c7SMaximilian Luz  * Return: Returns zero on success or if no request has been executed, the
2003c167b9c7SMaximilian Luz  * status of the executed SAM request if that request failed, or %-EPROTO if
2004c167b9c7SMaximilian Luz  * an unexpected response has been received.
2005c167b9c7SMaximilian Luz  */
ssam_ctrl_notif_display_off(struct ssam_controller * ctrl)2006c167b9c7SMaximilian Luz int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl)
2007c167b9c7SMaximilian Luz {
2008c167b9c7SMaximilian Luz 	int status;
2009c167b9c7SMaximilian Luz 	u8 response;
2010c167b9c7SMaximilian Luz 
2011c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: notifying display off\n");
2012c167b9c7SMaximilian Luz 
2013c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_notif_display_off, ctrl, &response);
2014c167b9c7SMaximilian Luz 	if (status)
2015c167b9c7SMaximilian Luz 		return status;
2016c167b9c7SMaximilian Luz 
2017c167b9c7SMaximilian Luz 	if (response != 0) {
2018c167b9c7SMaximilian Luz 		ssam_err(ctrl, "unexpected response from display-off notification: %#04x\n",
2019c167b9c7SMaximilian Luz 			 response);
2020c167b9c7SMaximilian Luz 		return -EPROTO;
2021c167b9c7SMaximilian Luz 	}
2022c167b9c7SMaximilian Luz 
2023c167b9c7SMaximilian Luz 	return 0;
2024c167b9c7SMaximilian Luz }
2025c167b9c7SMaximilian Luz 
2026c167b9c7SMaximilian Luz /**
2027c167b9c7SMaximilian Luz  * ssam_ctrl_notif_display_on() - Notify EC that the display has been turned on.
2028c167b9c7SMaximilian Luz  * @ctrl: The controller.
2029c167b9c7SMaximilian Luz  *
2030c167b9c7SMaximilian Luz  * Notify the EC that the display has been turned back on and the driver has
2031c167b9c7SMaximilian Luz  * exited its lower-power state. This notification is the counterpart to the
2032c167b9c7SMaximilian Luz  * display-off notification sent via ssam_ctrl_notif_display_off() and will
2033c167b9c7SMaximilian Luz  * reverse its effects, including resetting events to their default behavior.
2034c167b9c7SMaximilian Luz  *
2035c167b9c7SMaximilian Luz  * This function will only send the display-on notification command if display
2036c167b9c7SMaximilian Luz  * notifications are supported by the EC. Currently all known devices support
2037c167b9c7SMaximilian Luz  * these notifications.
2038c167b9c7SMaximilian Luz  *
2039c167b9c7SMaximilian Luz  * See ssam_ctrl_notif_display_off() for more details.
2040c167b9c7SMaximilian Luz  *
2041c167b9c7SMaximilian Luz  * Return: Returns zero on success or if no request has been executed, the
2042c167b9c7SMaximilian Luz  * status of the executed SAM request if that request failed, or %-EPROTO if
2043c167b9c7SMaximilian Luz  * an unexpected response has been received.
2044c167b9c7SMaximilian Luz  */
ssam_ctrl_notif_display_on(struct ssam_controller * ctrl)2045c167b9c7SMaximilian Luz int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl)
2046c167b9c7SMaximilian Luz {
2047c167b9c7SMaximilian Luz 	int status;
2048c167b9c7SMaximilian Luz 	u8 response;
2049c167b9c7SMaximilian Luz 
2050c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: notifying display on\n");
2051c167b9c7SMaximilian Luz 
2052c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_notif_display_on, ctrl, &response);
2053c167b9c7SMaximilian Luz 	if (status)
2054c167b9c7SMaximilian Luz 		return status;
2055c167b9c7SMaximilian Luz 
2056c167b9c7SMaximilian Luz 	if (response != 0) {
2057c167b9c7SMaximilian Luz 		ssam_err(ctrl, "unexpected response from display-on notification: %#04x\n",
2058c167b9c7SMaximilian Luz 			 response);
2059c167b9c7SMaximilian Luz 		return -EPROTO;
2060c167b9c7SMaximilian Luz 	}
2061c167b9c7SMaximilian Luz 
2062c167b9c7SMaximilian Luz 	return 0;
2063c167b9c7SMaximilian Luz }
2064c167b9c7SMaximilian Luz 
2065c167b9c7SMaximilian Luz /**
2066c167b9c7SMaximilian Luz  * ssam_ctrl_notif_d0_exit() - Notify EC that the driver/device exits the D0
2067c167b9c7SMaximilian Luz  * power state.
2068c167b9c7SMaximilian Luz  * @ctrl: The controller
2069c167b9c7SMaximilian Luz  *
2070c167b9c7SMaximilian Luz  * Notifies the EC that the driver prepares to exit the D0 power state in
2071c167b9c7SMaximilian Luz  * favor of a lower-power state. Exact effects of this function related to the
2072c167b9c7SMaximilian Luz  * EC are currently unknown.
2073c167b9c7SMaximilian Luz  *
2074c167b9c7SMaximilian Luz  * This function will only send the D0-exit notification command if D0-state
2075c167b9c7SMaximilian Luz  * notifications are supported by the EC. Only newer Surface generations
2076c167b9c7SMaximilian Luz  * support these notifications.
2077c167b9c7SMaximilian Luz  *
2078c167b9c7SMaximilian Luz  * Use ssam_ctrl_notif_d0_entry() to reverse the effects of this function.
2079c167b9c7SMaximilian Luz  *
2080c167b9c7SMaximilian Luz  * Return: Returns zero on success or if no request has been executed, the
2081c167b9c7SMaximilian Luz  * status of the executed SAM request if that request failed, or %-EPROTO if
2082c167b9c7SMaximilian Luz  * an unexpected response has been received.
2083c167b9c7SMaximilian Luz  */
ssam_ctrl_notif_d0_exit(struct ssam_controller * ctrl)2084c167b9c7SMaximilian Luz int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl)
2085c167b9c7SMaximilian Luz {
2086c167b9c7SMaximilian Luz 	int status;
2087c167b9c7SMaximilian Luz 	u8 response;
2088c167b9c7SMaximilian Luz 
2089c167b9c7SMaximilian Luz 	if (!ctrl->caps.d3_closes_handle)
2090c167b9c7SMaximilian Luz 		return 0;
2091c167b9c7SMaximilian Luz 
2092c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: notifying D0 exit\n");
2093c167b9c7SMaximilian Luz 
2094c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_notif_d0_exit, ctrl, &response);
2095c167b9c7SMaximilian Luz 	if (status)
2096c167b9c7SMaximilian Luz 		return status;
2097c167b9c7SMaximilian Luz 
2098c167b9c7SMaximilian Luz 	if (response != 0) {
2099c167b9c7SMaximilian Luz 		ssam_err(ctrl, "unexpected response from D0-exit notification: %#04x\n",
2100c167b9c7SMaximilian Luz 			 response);
2101c167b9c7SMaximilian Luz 		return -EPROTO;
2102c167b9c7SMaximilian Luz 	}
2103c167b9c7SMaximilian Luz 
2104c167b9c7SMaximilian Luz 	return 0;
2105c167b9c7SMaximilian Luz }
2106c167b9c7SMaximilian Luz 
2107c167b9c7SMaximilian Luz /**
2108c167b9c7SMaximilian Luz  * ssam_ctrl_notif_d0_entry() - Notify EC that the driver/device enters the D0
2109c167b9c7SMaximilian Luz  * power state.
2110c167b9c7SMaximilian Luz  * @ctrl: The controller
2111c167b9c7SMaximilian Luz  *
2112c167b9c7SMaximilian Luz  * Notifies the EC that the driver has exited a lower-power state and entered
2113c167b9c7SMaximilian Luz  * the D0 power state. Exact effects of this function related to the EC are
2114c167b9c7SMaximilian Luz  * currently unknown.
2115c167b9c7SMaximilian Luz  *
2116c167b9c7SMaximilian Luz  * This function will only send the D0-entry notification command if D0-state
2117c167b9c7SMaximilian Luz  * notifications are supported by the EC. Only newer Surface generations
2118c167b9c7SMaximilian Luz  * support these notifications.
2119c167b9c7SMaximilian Luz  *
2120c167b9c7SMaximilian Luz  * See ssam_ctrl_notif_d0_exit() for more details.
2121c167b9c7SMaximilian Luz  *
2122c167b9c7SMaximilian Luz  * Return: Returns zero on success or if no request has been executed, the
2123c167b9c7SMaximilian Luz  * status of the executed SAM request if that request failed, or %-EPROTO if
2124c167b9c7SMaximilian Luz  * an unexpected response has been received.
2125c167b9c7SMaximilian Luz  */
ssam_ctrl_notif_d0_entry(struct ssam_controller * ctrl)2126c167b9c7SMaximilian Luz int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
2127c167b9c7SMaximilian Luz {
2128c167b9c7SMaximilian Luz 	int status;
2129c167b9c7SMaximilian Luz 	u8 response;
2130c167b9c7SMaximilian Luz 
2131c167b9c7SMaximilian Luz 	if (!ctrl->caps.d3_closes_handle)
2132c167b9c7SMaximilian Luz 		return 0;
2133c167b9c7SMaximilian Luz 
2134c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: notifying D0 entry\n");
2135c167b9c7SMaximilian Luz 
2136c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_notif_d0_entry, ctrl, &response);
2137c167b9c7SMaximilian Luz 	if (status)
2138c167b9c7SMaximilian Luz 		return status;
2139c167b9c7SMaximilian Luz 
2140c167b9c7SMaximilian Luz 	if (response != 0) {
2141c167b9c7SMaximilian Luz 		ssam_err(ctrl, "unexpected response from D0-entry notification: %#04x\n",
2142c167b9c7SMaximilian Luz 			 response);
2143c167b9c7SMaximilian Luz 		return -EPROTO;
2144c167b9c7SMaximilian Luz 	}
2145c167b9c7SMaximilian Luz 
2146c167b9c7SMaximilian Luz 	return 0;
2147c167b9c7SMaximilian Luz }
2148c167b9c7SMaximilian Luz 
2149c167b9c7SMaximilian Luz 
2150c167b9c7SMaximilian Luz /* -- Top-level event registry interface. ----------------------------------- */
2151c167b9c7SMaximilian Luz 
2152c167b9c7SMaximilian Luz /**
21534b38a1dcSMaximilian Luz  * ssam_nf_refcount_enable() - Enable event for reference count entry if it has
21544b38a1dcSMaximilian Luz  * not already been enabled.
21554b38a1dcSMaximilian Luz  * @ctrl:  The controller to enable the event on.
21564b38a1dcSMaximilian Luz  * @entry: The reference count entry for the event to be enabled.
21574b38a1dcSMaximilian Luz  * @flags: The flags used for enabling the event on the EC.
21584b38a1dcSMaximilian Luz  *
21594b38a1dcSMaximilian Luz  * Enable the event associated with the given reference count entry if the
21604b38a1dcSMaximilian Luz  * reference count equals one, i.e. the event has not previously been enabled.
21614b38a1dcSMaximilian Luz  * If the event has already been enabled (i.e. reference count not equal to
21624b38a1dcSMaximilian Luz  * one), check that the flags used for enabling match and warn about this if
21634b38a1dcSMaximilian Luz  * they do not.
21644b38a1dcSMaximilian Luz  *
21654b38a1dcSMaximilian Luz  * This does not modify the reference count itself, which is done with
21664b38a1dcSMaximilian Luz  * ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
21674b38a1dcSMaximilian Luz  *
21684b38a1dcSMaximilian Luz  * Note: ``nf->lock`` must be held when calling this function.
21694b38a1dcSMaximilian Luz  *
21704b38a1dcSMaximilian Luz  * Return: Returns zero on success. If the event is enabled by this call,
21714b38a1dcSMaximilian Luz  * returns the status of the event-enable EC command.
21724b38a1dcSMaximilian Luz  */
ssam_nf_refcount_enable(struct ssam_controller * ctrl,struct ssam_nf_refcount_entry * entry,u8 flags)21734b38a1dcSMaximilian Luz static int ssam_nf_refcount_enable(struct ssam_controller *ctrl,
21744b38a1dcSMaximilian Luz 				   struct ssam_nf_refcount_entry *entry, u8 flags)
21754b38a1dcSMaximilian Luz {
21764b38a1dcSMaximilian Luz 	const struct ssam_event_registry reg = entry->key.reg;
21774b38a1dcSMaximilian Luz 	const struct ssam_event_id id = entry->key.id;
21784b38a1dcSMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
21794b38a1dcSMaximilian Luz 	int status;
21804b38a1dcSMaximilian Luz 
21814b38a1dcSMaximilian Luz 	lockdep_assert_held(&nf->lock);
21824b38a1dcSMaximilian Luz 
21834b38a1dcSMaximilian Luz 	ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
21844b38a1dcSMaximilian Luz 		 reg.target_category, id.target_category, id.instance, entry->refcount);
21854b38a1dcSMaximilian Luz 
21864b38a1dcSMaximilian Luz 	if (entry->refcount == 1) {
21874b38a1dcSMaximilian Luz 		status = ssam_ssh_event_enable(ctrl, reg, id, flags);
21884b38a1dcSMaximilian Luz 		if (status)
21894b38a1dcSMaximilian Luz 			return status;
21904b38a1dcSMaximilian Luz 
21914b38a1dcSMaximilian Luz 		entry->flags = flags;
21924b38a1dcSMaximilian Luz 
21934b38a1dcSMaximilian Luz 	} else if (entry->flags != flags) {
21944b38a1dcSMaximilian Luz 		ssam_warn(ctrl,
21954b38a1dcSMaximilian Luz 			  "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
21964b38a1dcSMaximilian Luz 			  flags, entry->flags, reg.target_category, id.target_category,
21974b38a1dcSMaximilian Luz 			  id.instance);
21984b38a1dcSMaximilian Luz 	}
21994b38a1dcSMaximilian Luz 
22004b38a1dcSMaximilian Luz 	return 0;
22014b38a1dcSMaximilian Luz }
22024b38a1dcSMaximilian Luz 
22034b38a1dcSMaximilian Luz /**
22045c1e88b9SMaximilian Luz  * ssam_nf_refcount_disable_free() - Disable event for reference count entry if
22055c1e88b9SMaximilian Luz  * it is no longer in use and free the corresponding entry.
22064b38a1dcSMaximilian Luz  * @ctrl:  The controller to disable the event on.
22074b38a1dcSMaximilian Luz  * @entry: The reference count entry for the event to be disabled.
22084b38a1dcSMaximilian Luz  * @flags: The flags used for enabling the event on the EC.
22095c1e88b9SMaximilian Luz  * @ec:    Flag specifying if the event should actually be disabled on the EC.
22104b38a1dcSMaximilian Luz  *
22115c1e88b9SMaximilian Luz  * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the
22125c1e88b9SMaximilian Luz  * event is no longer requested by any client), the specified event will be
22135c1e88b9SMaximilian Luz  * disabled on the EC via the corresponding request.
22145c1e88b9SMaximilian Luz  *
22155c1e88b9SMaximilian Luz  * If ``ec`` equals ``false``, no request will be sent to the EC and the event
22165c1e88b9SMaximilian Luz  * can be considered in a detached state (i.e. no longer used but still
22175c1e88b9SMaximilian Luz  * enabled). Disabling an event via this method may be required for
22185c1e88b9SMaximilian Luz  * hot-removable devices, where event disable requests may time out after the
22195c1e88b9SMaximilian Luz  * device has been physically removed.
22205c1e88b9SMaximilian Luz  *
22215c1e88b9SMaximilian Luz  * In both cases, if the reference count equals zero, the corresponding
22225c1e88b9SMaximilian Luz  * reference count entry will be freed. The reference count entry must not be
22235c1e88b9SMaximilian Luz  * used any more after a call to this function.
22244b38a1dcSMaximilian Luz  *
22254b38a1dcSMaximilian Luz  * Also checks if the flags used for disabling the event match the flags used
22264b38a1dcSMaximilian Luz  * for enabling the event and warns if they do not (regardless of reference
22274b38a1dcSMaximilian Luz  * count).
22284b38a1dcSMaximilian Luz  *
22294b38a1dcSMaximilian Luz  * This does not modify the reference count itself, which is done with
22304b38a1dcSMaximilian Luz  * ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
22314b38a1dcSMaximilian Luz  *
22324b38a1dcSMaximilian Luz  * Note: ``nf->lock`` must be held when calling this function.
22334b38a1dcSMaximilian Luz  *
22344b38a1dcSMaximilian Luz  * Return: Returns zero on success. If the event is disabled by this call,
22354b38a1dcSMaximilian Luz  * returns the status of the event-enable EC command.
22364b38a1dcSMaximilian Luz  */
ssam_nf_refcount_disable_free(struct ssam_controller * ctrl,struct ssam_nf_refcount_entry * entry,u8 flags,bool ec)22374b38a1dcSMaximilian Luz static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
22385c1e88b9SMaximilian Luz 					 struct ssam_nf_refcount_entry *entry, u8 flags, bool ec)
22394b38a1dcSMaximilian Luz {
22404b38a1dcSMaximilian Luz 	const struct ssam_event_registry reg = entry->key.reg;
22414b38a1dcSMaximilian Luz 	const struct ssam_event_id id = entry->key.id;
22424b38a1dcSMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
224337ed76a7SMaximilian Luz 	int status = 0;
22444b38a1dcSMaximilian Luz 
22454b38a1dcSMaximilian Luz 	lockdep_assert_held(&nf->lock);
22464b38a1dcSMaximilian Luz 
22475c1e88b9SMaximilian Luz 	ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
22485c1e88b9SMaximilian Luz 		 ec ? "disabling" : "detaching", reg.target_category, id.target_category,
22495c1e88b9SMaximilian Luz 		 id.instance, entry->refcount);
22504b38a1dcSMaximilian Luz 
22514b38a1dcSMaximilian Luz 	if (entry->flags != flags) {
22524b38a1dcSMaximilian Luz 		ssam_warn(ctrl,
22534b38a1dcSMaximilian Luz 			  "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
22544b38a1dcSMaximilian Luz 			  flags, entry->flags, reg.target_category, id.target_category,
22554b38a1dcSMaximilian Luz 			  id.instance);
22564b38a1dcSMaximilian Luz 	}
22574b38a1dcSMaximilian Luz 
22585c1e88b9SMaximilian Luz 	if (ec && entry->refcount == 0) {
22594b38a1dcSMaximilian Luz 		status = ssam_ssh_event_disable(ctrl, reg, id, flags);
22604b38a1dcSMaximilian Luz 		kfree(entry);
22614b38a1dcSMaximilian Luz 	}
22624b38a1dcSMaximilian Luz 
22634b38a1dcSMaximilian Luz 	return status;
22644b38a1dcSMaximilian Luz }
22654b38a1dcSMaximilian Luz 
22664b38a1dcSMaximilian Luz /**
2267c167b9c7SMaximilian Luz  * ssam_notifier_register() - Register an event notifier.
2268c167b9c7SMaximilian Luz  * @ctrl: The controller to register the notifier on.
2269c167b9c7SMaximilian Luz  * @n:    The event notifier to register.
2270c167b9c7SMaximilian Luz  *
22710e8512faSMaximilian Luz  * Register an event notifier. Increment the usage counter of the associated
22720e8512faSMaximilian Luz  * SAM event if the notifier is not marked as an observer. If the event is not
22730e8512faSMaximilian Luz  * marked as an observer and is currently not enabled, it will be enabled
22740e8512faSMaximilian Luz  * during this call. If the notifier is marked as an observer, no attempt will
22750e8512faSMaximilian Luz  * be made at enabling any event and no reference count will be modified.
22760e8512faSMaximilian Luz  *
22770e8512faSMaximilian Luz  * Notifiers marked as observers do not need to be associated with one specific
22780e8512faSMaximilian Luz  * event, i.e. as long as no event matching is performed, only the event target
22790e8512faSMaximilian Luz  * category needs to be set.
2280c167b9c7SMaximilian Luz  *
2281c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-ENOSPC if there have already been
2282c167b9c7SMaximilian Luz  * %INT_MAX notifiers for the event ID/type associated with the notifier block
2283c167b9c7SMaximilian Luz  * registered, %-ENOMEM if the corresponding event entry could not be
2284c167b9c7SMaximilian Luz  * allocated. If this is the first time that a notifier block is registered
2285c167b9c7SMaximilian Luz  * for the specific associated event, returns the status of the event-enable
2286c167b9c7SMaximilian Luz  * EC-command.
2287c167b9c7SMaximilian Luz  */
ssam_notifier_register(struct ssam_controller * ctrl,struct ssam_event_notifier * n)22880e8512faSMaximilian Luz int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n)
2289c167b9c7SMaximilian Luz {
2290c167b9c7SMaximilian Luz 	u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
22910e8512faSMaximilian Luz 	struct ssam_nf_refcount_entry *entry = NULL;
2292c167b9c7SMaximilian Luz 	struct ssam_nf_head *nf_head;
2293c167b9c7SMaximilian Luz 	struct ssam_nf *nf;
2294c167b9c7SMaximilian Luz 	int status;
2295c167b9c7SMaximilian Luz 
2296c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid))
2297c167b9c7SMaximilian Luz 		return -EINVAL;
2298c167b9c7SMaximilian Luz 
2299c167b9c7SMaximilian Luz 	nf = &ctrl->cplt.event.notif;
2300c167b9c7SMaximilian Luz 	nf_head = &nf->head[ssh_rqid_to_event(rqid)];
2301c167b9c7SMaximilian Luz 
2302c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2303c167b9c7SMaximilian Luz 
23040e8512faSMaximilian Luz 	if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
2305c167b9c7SMaximilian Luz 		entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id);
2306c167b9c7SMaximilian Luz 		if (IS_ERR(entry)) {
2307c167b9c7SMaximilian Luz 			mutex_unlock(&nf->lock);
2308c167b9c7SMaximilian Luz 			return PTR_ERR(entry);
2309c167b9c7SMaximilian Luz 		}
23100e8512faSMaximilian Luz 	}
2311c167b9c7SMaximilian Luz 
2312c167b9c7SMaximilian Luz 	status = ssam_nfblk_insert(nf_head, &n->base);
2313c167b9c7SMaximilian Luz 	if (status) {
23144b38a1dcSMaximilian Luz 		if (entry)
23154b38a1dcSMaximilian Luz 			ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
2316c167b9c7SMaximilian Luz 
2317c167b9c7SMaximilian Luz 		mutex_unlock(&nf->lock);
2318c167b9c7SMaximilian Luz 		return status;
2319c167b9c7SMaximilian Luz 	}
2320c167b9c7SMaximilian Luz 
23214b38a1dcSMaximilian Luz 	if (entry) {
23224b38a1dcSMaximilian Luz 		status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags);
2323c167b9c7SMaximilian Luz 		if (status) {
2324c167b9c7SMaximilian Luz 			ssam_nfblk_remove(&n->base);
23254b38a1dcSMaximilian Luz 			ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
2326c167b9c7SMaximilian Luz 			mutex_unlock(&nf->lock);
2327c167b9c7SMaximilian Luz 			synchronize_srcu(&nf_head->srcu);
2328c167b9c7SMaximilian Luz 			return status;
2329c167b9c7SMaximilian Luz 		}
2330c167b9c7SMaximilian Luz 	}
2331c167b9c7SMaximilian Luz 
2332c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2333c167b9c7SMaximilian Luz 	return 0;
2334c167b9c7SMaximilian Luz }
2335c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_notifier_register);
2336c167b9c7SMaximilian Luz 
2337c167b9c7SMaximilian Luz /**
23385c1e88b9SMaximilian Luz  * __ssam_notifier_unregister() - Unregister an event notifier.
2339c167b9c7SMaximilian Luz  * @ctrl:    The controller the notifier has been registered on.
2340c167b9c7SMaximilian Luz  * @n:       The event notifier to unregister.
23415c1e88b9SMaximilian Luz  * @disable: Whether to disable the corresponding event on the EC.
2342c167b9c7SMaximilian Luz  *
23430e8512faSMaximilian Luz  * Unregister an event notifier. Decrement the usage counter of the associated
23440e8512faSMaximilian Luz  * SAM event if the notifier is not marked as an observer. If the usage counter
23455c1e88b9SMaximilian Luz  * reaches zero and ``disable`` equals ``true``, the event will be disabled.
23465c1e88b9SMaximilian Luz  *
23475c1e88b9SMaximilian Luz  * Useful for hot-removable devices, where communication may fail once the
23485c1e88b9SMaximilian Luz  * device has been physically removed. In that case, specifying ``disable`` as
23495c1e88b9SMaximilian Luz  * ``false`` avoids communication with the EC.
2350c167b9c7SMaximilian Luz  *
2351c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-ENOENT if the given notifier block has
2352c167b9c7SMaximilian Luz  * not been registered on the controller. If the given notifier block was the
2353c167b9c7SMaximilian Luz  * last one associated with its specific event, returns the status of the
2354c167b9c7SMaximilian Luz  * event-disable EC-command.
2355c167b9c7SMaximilian Luz  */
__ssam_notifier_unregister(struct ssam_controller * ctrl,struct ssam_event_notifier * n,bool disable)23565c1e88b9SMaximilian Luz int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n,
23575c1e88b9SMaximilian Luz 			       bool disable)
2358c167b9c7SMaximilian Luz {
2359c167b9c7SMaximilian Luz 	u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
2360c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
2361c167b9c7SMaximilian Luz 	struct ssam_nf_head *nf_head;
2362c167b9c7SMaximilian Luz 	struct ssam_nf *nf;
2363c167b9c7SMaximilian Luz 	int status = 0;
2364c167b9c7SMaximilian Luz 
2365c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid))
2366c167b9c7SMaximilian Luz 		return -EINVAL;
2367c167b9c7SMaximilian Luz 
2368c167b9c7SMaximilian Luz 	nf = &ctrl->cplt.event.notif;
2369c167b9c7SMaximilian Luz 	nf_head = &nf->head[ssh_rqid_to_event(rqid)];
2370c167b9c7SMaximilian Luz 
2371c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2372c167b9c7SMaximilian Luz 
2373c167b9c7SMaximilian Luz 	if (!ssam_nfblk_find(nf_head, &n->base)) {
2374c167b9c7SMaximilian Luz 		mutex_unlock(&nf->lock);
2375c167b9c7SMaximilian Luz 		return -ENOENT;
2376c167b9c7SMaximilian Luz 	}
2377c167b9c7SMaximilian Luz 
23780e8512faSMaximilian Luz 	/*
23790e8512faSMaximilian Luz 	 * If this is an observer notifier, do not attempt to disable the
23800e8512faSMaximilian Luz 	 * event, just remove it.
23810e8512faSMaximilian Luz 	 */
23824b38a1dcSMaximilian Luz 	if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
2383c167b9c7SMaximilian Luz 		entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
2384c167b9c7SMaximilian Luz 		if (WARN_ON(!entry)) {
2385c167b9c7SMaximilian Luz 			/*
23864b38a1dcSMaximilian Luz 			 * If this does not return an entry, there's a logic
23874b38a1dcSMaximilian Luz 			 * error somewhere: The notifier block is registered,
23884b38a1dcSMaximilian Luz 			 * but the event refcount entry is not there. Remove
23894b38a1dcSMaximilian Luz 			 * the notifier block anyways.
2390c167b9c7SMaximilian Luz 			 */
2391c167b9c7SMaximilian Luz 			status = -ENOENT;
2392c167b9c7SMaximilian Luz 			goto remove;
2393c167b9c7SMaximilian Luz 		}
2394c167b9c7SMaximilian Luz 
23955c1e88b9SMaximilian Luz 		status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable);
2396c167b9c7SMaximilian Luz 	}
2397c167b9c7SMaximilian Luz 
2398c167b9c7SMaximilian Luz remove:
2399c167b9c7SMaximilian Luz 	ssam_nfblk_remove(&n->base);
2400c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2401c167b9c7SMaximilian Luz 	synchronize_srcu(&nf_head->srcu);
2402c167b9c7SMaximilian Luz 
2403c167b9c7SMaximilian Luz 	return status;
2404c167b9c7SMaximilian Luz }
24055c1e88b9SMaximilian Luz EXPORT_SYMBOL_GPL(__ssam_notifier_unregister);
2406c167b9c7SMaximilian Luz 
2407c167b9c7SMaximilian Luz /**
24084b38a1dcSMaximilian Luz  * ssam_controller_event_enable() - Enable the specified event.
24094b38a1dcSMaximilian Luz  * @ctrl:  The controller to enable the event for.
24104b38a1dcSMaximilian Luz  * @reg:   The event registry to use for enabling the event.
24114b38a1dcSMaximilian Luz  * @id:    The event ID specifying the event to be enabled.
24124b38a1dcSMaximilian Luz  * @flags: The SAM event flags used for enabling the event.
24134b38a1dcSMaximilian Luz  *
24144b38a1dcSMaximilian Luz  * Increment the event reference count of the specified event. If the event has
24154b38a1dcSMaximilian Luz  * not been enabled previously, it will be enabled by this call.
24164b38a1dcSMaximilian Luz  *
24174b38a1dcSMaximilian Luz  * Note: In general, ssam_notifier_register() with a non-observer notifier
24184b38a1dcSMaximilian Luz  * should be preferred for enabling/disabling events, as this will guarantee
24194b38a1dcSMaximilian Luz  * proper ordering and event forwarding in case of errors during event
24204b38a1dcSMaximilian Luz  * enabling/disabling.
24214b38a1dcSMaximilian Luz  *
24224b38a1dcSMaximilian Luz  * Return: Returns zero on success, %-ENOSPC if the reference count for the
24234b38a1dcSMaximilian Luz  * specified event has reached its maximum, %-ENOMEM if the corresponding event
24244b38a1dcSMaximilian Luz  * entry could not be allocated. If this is the first time that this event has
24254b38a1dcSMaximilian Luz  * been enabled (i.e. the reference count was incremented from zero to one by
24264b38a1dcSMaximilian Luz  * this call), returns the status of the event-enable EC-command.
24274b38a1dcSMaximilian Luz  */
ssam_controller_event_enable(struct ssam_controller * ctrl,struct ssam_event_registry reg,struct ssam_event_id id,u8 flags)24284b38a1dcSMaximilian Luz int ssam_controller_event_enable(struct ssam_controller *ctrl,
24294b38a1dcSMaximilian Luz 				 struct ssam_event_registry reg,
24304b38a1dcSMaximilian Luz 				 struct ssam_event_id id, u8 flags)
24314b38a1dcSMaximilian Luz {
24324b38a1dcSMaximilian Luz 	u16 rqid = ssh_tc_to_rqid(id.target_category);
24334b38a1dcSMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
24344b38a1dcSMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
24354b38a1dcSMaximilian Luz 	int status;
24364b38a1dcSMaximilian Luz 
24374b38a1dcSMaximilian Luz 	if (!ssh_rqid_is_event(rqid))
24384b38a1dcSMaximilian Luz 		return -EINVAL;
24394b38a1dcSMaximilian Luz 
24404b38a1dcSMaximilian Luz 	mutex_lock(&nf->lock);
24414b38a1dcSMaximilian Luz 
24424b38a1dcSMaximilian Luz 	entry = ssam_nf_refcount_inc(nf, reg, id);
24434b38a1dcSMaximilian Luz 	if (IS_ERR(entry)) {
24444b38a1dcSMaximilian Luz 		mutex_unlock(&nf->lock);
24454b38a1dcSMaximilian Luz 		return PTR_ERR(entry);
24464b38a1dcSMaximilian Luz 	}
24474b38a1dcSMaximilian Luz 
24484b38a1dcSMaximilian Luz 	status = ssam_nf_refcount_enable(ctrl, entry, flags);
24494b38a1dcSMaximilian Luz 	if (status) {
24504b38a1dcSMaximilian Luz 		ssam_nf_refcount_dec_free(nf, reg, id);
24514b38a1dcSMaximilian Luz 		mutex_unlock(&nf->lock);
24524b38a1dcSMaximilian Luz 		return status;
24534b38a1dcSMaximilian Luz 	}
24544b38a1dcSMaximilian Luz 
24554b38a1dcSMaximilian Luz 	mutex_unlock(&nf->lock);
24564b38a1dcSMaximilian Luz 	return 0;
24574b38a1dcSMaximilian Luz }
24584b38a1dcSMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_event_enable);
24594b38a1dcSMaximilian Luz 
24604b38a1dcSMaximilian Luz /**
24614b38a1dcSMaximilian Luz  * ssam_controller_event_disable() - Disable the specified event.
24624b38a1dcSMaximilian Luz  * @ctrl:  The controller to disable the event for.
24634b38a1dcSMaximilian Luz  * @reg:   The event registry to use for disabling the event.
24644b38a1dcSMaximilian Luz  * @id:    The event ID specifying the event to be disabled.
24654b38a1dcSMaximilian Luz  * @flags: The flags used when enabling the event.
24664b38a1dcSMaximilian Luz  *
24674b38a1dcSMaximilian Luz  * Decrement the reference count of the specified event. If the reference count
24684b38a1dcSMaximilian Luz  * reaches zero, the event will be disabled.
24694b38a1dcSMaximilian Luz  *
24704b38a1dcSMaximilian Luz  * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a
24714b38a1dcSMaximilian Luz  * non-observer notifier should be preferred for enabling/disabling events, as
24724b38a1dcSMaximilian Luz  * this will guarantee proper ordering and event forwarding in case of errors
24734b38a1dcSMaximilian Luz  * during event enabling/disabling.
24744b38a1dcSMaximilian Luz  *
24754b38a1dcSMaximilian Luz  * Return: Returns zero on success, %-ENOENT if the given event has not been
24764b38a1dcSMaximilian Luz  * enabled on the controller. If the reference count of the event reaches zero
24774b38a1dcSMaximilian Luz  * during this call, returns the status of the event-disable EC-command.
24784b38a1dcSMaximilian Luz  */
ssam_controller_event_disable(struct ssam_controller * ctrl,struct ssam_event_registry reg,struct ssam_event_id id,u8 flags)24794b38a1dcSMaximilian Luz int ssam_controller_event_disable(struct ssam_controller *ctrl,
24804b38a1dcSMaximilian Luz 				  struct ssam_event_registry reg,
24814b38a1dcSMaximilian Luz 				  struct ssam_event_id id, u8 flags)
24824b38a1dcSMaximilian Luz {
24834b38a1dcSMaximilian Luz 	u16 rqid = ssh_tc_to_rqid(id.target_category);
24844b38a1dcSMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
24854b38a1dcSMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
2486f9e7f9a2SMaximilian Luz 	int status;
24874b38a1dcSMaximilian Luz 
24884b38a1dcSMaximilian Luz 	if (!ssh_rqid_is_event(rqid))
24894b38a1dcSMaximilian Luz 		return -EINVAL;
24904b38a1dcSMaximilian Luz 
24914b38a1dcSMaximilian Luz 	mutex_lock(&nf->lock);
24924b38a1dcSMaximilian Luz 
24934b38a1dcSMaximilian Luz 	entry = ssam_nf_refcount_dec(nf, reg, id);
24944b38a1dcSMaximilian Luz 	if (!entry) {
24954b38a1dcSMaximilian Luz 		mutex_unlock(&nf->lock);
24964b38a1dcSMaximilian Luz 		return -ENOENT;
24974b38a1dcSMaximilian Luz 	}
24984b38a1dcSMaximilian Luz 
24995c1e88b9SMaximilian Luz 	status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true);
25004b38a1dcSMaximilian Luz 
25014b38a1dcSMaximilian Luz 	mutex_unlock(&nf->lock);
25024b38a1dcSMaximilian Luz 	return status;
25034b38a1dcSMaximilian Luz }
25044b38a1dcSMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_event_disable);
25054b38a1dcSMaximilian Luz 
25064b38a1dcSMaximilian Luz /**
2507c167b9c7SMaximilian Luz  * ssam_notifier_disable_registered() - Disable events for all registered
2508c167b9c7SMaximilian Luz  * notifiers.
2509c167b9c7SMaximilian Luz  * @ctrl: The controller for which to disable the notifiers/events.
2510c167b9c7SMaximilian Luz  *
2511c167b9c7SMaximilian Luz  * Disables events for all currently registered notifiers. In case of an error
2512c167b9c7SMaximilian Luz  * (EC command failing), all previously disabled events will be restored and
2513c167b9c7SMaximilian Luz  * the error code returned.
2514c167b9c7SMaximilian Luz  *
2515c167b9c7SMaximilian Luz  * This function is intended to disable all events prior to hibernation entry.
2516c167b9c7SMaximilian Luz  * See ssam_notifier_restore_registered() to restore/re-enable all events
2517c167b9c7SMaximilian Luz  * disabled with this function.
2518c167b9c7SMaximilian Luz  *
2519c167b9c7SMaximilian Luz  * Note that this function will not disable events for notifiers registered
2520c167b9c7SMaximilian Luz  * after calling this function. It should thus be made sure that no new
2521c167b9c7SMaximilian Luz  * notifiers are going to be added after this call and before the corresponding
2522c167b9c7SMaximilian Luz  * call to ssam_notifier_restore_registered().
2523c167b9c7SMaximilian Luz  *
2524c167b9c7SMaximilian Luz  * Return: Returns zero on success. In case of failure returns the error code
2525c167b9c7SMaximilian Luz  * returned by the failed EC command to disable an event.
2526c167b9c7SMaximilian Luz  */
ssam_notifier_disable_registered(struct ssam_controller * ctrl)2527c167b9c7SMaximilian Luz int ssam_notifier_disable_registered(struct ssam_controller *ctrl)
2528c167b9c7SMaximilian Luz {
2529c167b9c7SMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
2530c167b9c7SMaximilian Luz 	struct rb_node *n;
2531c167b9c7SMaximilian Luz 	int status;
2532c167b9c7SMaximilian Luz 
2533c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2534c167b9c7SMaximilian Luz 	for (n = rb_first(&nf->refcount); n; n = rb_next(n)) {
2535c167b9c7SMaximilian Luz 		struct ssam_nf_refcount_entry *e;
2536c167b9c7SMaximilian Luz 
2537c167b9c7SMaximilian Luz 		e = rb_entry(n, struct ssam_nf_refcount_entry, node);
2538c167b9c7SMaximilian Luz 		status = ssam_ssh_event_disable(ctrl, e->key.reg,
2539c167b9c7SMaximilian Luz 						e->key.id, e->flags);
2540c167b9c7SMaximilian Luz 		if (status)
2541c167b9c7SMaximilian Luz 			goto err;
2542c167b9c7SMaximilian Luz 	}
2543c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2544c167b9c7SMaximilian Luz 
2545c167b9c7SMaximilian Luz 	return 0;
2546c167b9c7SMaximilian Luz 
2547c167b9c7SMaximilian Luz err:
2548c167b9c7SMaximilian Luz 	for (n = rb_prev(n); n; n = rb_prev(n)) {
2549c167b9c7SMaximilian Luz 		struct ssam_nf_refcount_entry *e;
2550c167b9c7SMaximilian Luz 
2551c167b9c7SMaximilian Luz 		e = rb_entry(n, struct ssam_nf_refcount_entry, node);
2552c167b9c7SMaximilian Luz 		ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags);
2553c167b9c7SMaximilian Luz 	}
2554c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2555c167b9c7SMaximilian Luz 
2556c167b9c7SMaximilian Luz 	return status;
2557c167b9c7SMaximilian Luz }
2558c167b9c7SMaximilian Luz 
2559c167b9c7SMaximilian Luz /**
2560c167b9c7SMaximilian Luz  * ssam_notifier_restore_registered() - Restore/re-enable events for all
2561c167b9c7SMaximilian Luz  * registered notifiers.
2562c167b9c7SMaximilian Luz  * @ctrl: The controller for which to restore the notifiers/events.
2563c167b9c7SMaximilian Luz  *
2564c167b9c7SMaximilian Luz  * Restores/re-enables all events for which notifiers have been registered on
2565c167b9c7SMaximilian Luz  * the given controller. In case of a failure, the error is logged and the
2566c167b9c7SMaximilian Luz  * function continues to try and enable the remaining events.
2567c167b9c7SMaximilian Luz  *
2568c167b9c7SMaximilian Luz  * This function is intended to restore/re-enable all registered events after
2569c167b9c7SMaximilian Luz  * hibernation. See ssam_notifier_disable_registered() for the counter part
2570c167b9c7SMaximilian Luz  * disabling the events and more details.
2571c167b9c7SMaximilian Luz  */
ssam_notifier_restore_registered(struct ssam_controller * ctrl)2572c167b9c7SMaximilian Luz void ssam_notifier_restore_registered(struct ssam_controller *ctrl)
2573c167b9c7SMaximilian Luz {
2574c167b9c7SMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
2575c167b9c7SMaximilian Luz 	struct rb_node *n;
2576c167b9c7SMaximilian Luz 
2577c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2578c167b9c7SMaximilian Luz 	for (n = rb_first(&nf->refcount); n; n = rb_next(n)) {
2579c167b9c7SMaximilian Luz 		struct ssam_nf_refcount_entry *e;
2580c167b9c7SMaximilian Luz 
2581c167b9c7SMaximilian Luz 		e = rb_entry(n, struct ssam_nf_refcount_entry, node);
2582c167b9c7SMaximilian Luz 
2583c167b9c7SMaximilian Luz 		/* Ignore errors, will get logged in call. */
2584c167b9c7SMaximilian Luz 		ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags);
2585c167b9c7SMaximilian Luz 	}
2586c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2587c167b9c7SMaximilian Luz }
2588c167b9c7SMaximilian Luz 
2589c167b9c7SMaximilian Luz /**
2590c167b9c7SMaximilian Luz  * ssam_notifier_is_empty() - Check if there are any registered notifiers.
2591c167b9c7SMaximilian Luz  * @ctrl: The controller to check on.
2592c167b9c7SMaximilian Luz  *
2593c167b9c7SMaximilian Luz  * Return: Returns %true if there are currently no notifiers registered on the
2594c167b9c7SMaximilian Luz  * controller, %false otherwise.
2595c167b9c7SMaximilian Luz  */
ssam_notifier_is_empty(struct ssam_controller * ctrl)2596c167b9c7SMaximilian Luz static bool ssam_notifier_is_empty(struct ssam_controller *ctrl)
2597c167b9c7SMaximilian Luz {
2598c167b9c7SMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
2599c167b9c7SMaximilian Luz 	bool result;
2600c167b9c7SMaximilian Luz 
2601c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2602c167b9c7SMaximilian Luz 	result = ssam_nf_refcount_empty(nf);
2603c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2604c167b9c7SMaximilian Luz 
2605c167b9c7SMaximilian Luz 	return result;
2606c167b9c7SMaximilian Luz }
2607c167b9c7SMaximilian Luz 
2608c167b9c7SMaximilian Luz /**
2609c167b9c7SMaximilian Luz  * ssam_notifier_unregister_all() - Unregister all currently registered
2610c167b9c7SMaximilian Luz  * notifiers.
2611c167b9c7SMaximilian Luz  * @ctrl: The controller to unregister the notifiers on.
2612c167b9c7SMaximilian Luz  *
2613c167b9c7SMaximilian Luz  * Unregisters all currently registered notifiers. This function is used to
2614c167b9c7SMaximilian Luz  * ensure that all notifiers will be unregistered and associated
2615c167b9c7SMaximilian Luz  * entries/resources freed when the controller is being shut down.
2616c167b9c7SMaximilian Luz  */
ssam_notifier_unregister_all(struct ssam_controller * ctrl)2617c167b9c7SMaximilian Luz static void ssam_notifier_unregister_all(struct ssam_controller *ctrl)
2618c167b9c7SMaximilian Luz {
2619c167b9c7SMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
2620c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *e, *n;
2621c167b9c7SMaximilian Luz 
2622c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2623c167b9c7SMaximilian Luz 	rbtree_postorder_for_each_entry_safe(e, n, &nf->refcount, node) {
2624c167b9c7SMaximilian Luz 		/* Ignore errors, will get logged in call. */
2625c167b9c7SMaximilian Luz 		ssam_ssh_event_disable(ctrl, e->key.reg, e->key.id, e->flags);
2626c167b9c7SMaximilian Luz 		kfree(e);
2627c167b9c7SMaximilian Luz 	}
2628c167b9c7SMaximilian Luz 	nf->refcount = RB_ROOT;
2629c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2630c167b9c7SMaximilian Luz }
2631c167b9c7SMaximilian Luz 
2632c167b9c7SMaximilian Luz 
2633c167b9c7SMaximilian Luz /* -- Wakeup IRQ. ----------------------------------------------------------- */
2634c167b9c7SMaximilian Luz 
ssam_irq_handle(int irq,void * dev_id)2635c167b9c7SMaximilian Luz static irqreturn_t ssam_irq_handle(int irq, void *dev_id)
2636c167b9c7SMaximilian Luz {
2637c167b9c7SMaximilian Luz 	struct ssam_controller *ctrl = dev_id;
2638c167b9c7SMaximilian Luz 
2639c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: wake irq triggered\n");
2640c167b9c7SMaximilian Luz 
2641c167b9c7SMaximilian Luz 	/*
2642c167b9c7SMaximilian Luz 	 * Note: Proper wakeup detection is currently unimplemented.
2643c167b9c7SMaximilian Luz 	 *       When the EC is in display-off or any other non-D0 state, it
2644c167b9c7SMaximilian Luz 	 *       does not send events/notifications to the host. Instead it
2645c167b9c7SMaximilian Luz 	 *       signals that there are events available via the wakeup IRQ.
2646c167b9c7SMaximilian Luz 	 *       This driver is responsible for calling back to the EC to
2647c167b9c7SMaximilian Luz 	 *       release these events one-by-one.
2648c167b9c7SMaximilian Luz 	 *
2649c167b9c7SMaximilian Luz 	 *       This IRQ should not cause a full system resume by its own.
2650c167b9c7SMaximilian Luz 	 *       Instead, events should be handled by their respective subsystem
2651c167b9c7SMaximilian Luz 	 *       drivers, which in turn should signal whether a full system
2652c167b9c7SMaximilian Luz 	 *       resume should be performed.
2653c167b9c7SMaximilian Luz 	 *
2654c167b9c7SMaximilian Luz 	 * TODO: Send GPIO callback command repeatedly to EC until callback
2655c167b9c7SMaximilian Luz 	 *       returns 0x00. Return flag of callback is "has more events".
2656c167b9c7SMaximilian Luz 	 *       Each time the command is sent, one event is "released". Once
2657c167b9c7SMaximilian Luz 	 *       all events have been released (return = 0x00), the GPIO is
2658c167b9c7SMaximilian Luz 	 *       re-armed. Detect wakeup events during this process, go back to
2659c167b9c7SMaximilian Luz 	 *       sleep if no wakeup event has been received.
2660c167b9c7SMaximilian Luz 	 */
2661c167b9c7SMaximilian Luz 
2662c167b9c7SMaximilian Luz 	return IRQ_HANDLED;
2663c167b9c7SMaximilian Luz }
2664c167b9c7SMaximilian Luz 
2665c167b9c7SMaximilian Luz /**
2666c167b9c7SMaximilian Luz  * ssam_irq_setup() - Set up SAM EC wakeup-GPIO interrupt.
2667c167b9c7SMaximilian Luz  * @ctrl: The controller for which the IRQ should be set up.
2668c167b9c7SMaximilian Luz  *
2669c167b9c7SMaximilian Luz  * Set up an IRQ for the wakeup-GPIO pin of the SAM EC. This IRQ can be used
2670c167b9c7SMaximilian Luz  * to wake the device from a low power state.
2671c167b9c7SMaximilian Luz  *
2672c167b9c7SMaximilian Luz  * Note that this IRQ can only be triggered while the EC is in the display-off
2673c167b9c7SMaximilian Luz  * state. In this state, events are not sent to the host in the usual way.
2674c167b9c7SMaximilian Luz  * Instead the wakeup-GPIO gets pulled to "high" as long as there are pending
2675c167b9c7SMaximilian Luz  * events and these events need to be released one-by-one via the GPIO
2676c167b9c7SMaximilian Luz  * callback request, either until there are no events left and the GPIO is
2677c167b9c7SMaximilian Luz  * reset, or all at once by transitioning the EC out of the display-off state,
2678c167b9c7SMaximilian Luz  * which will also clear the GPIO.
2679c167b9c7SMaximilian Luz  *
2680c167b9c7SMaximilian Luz  * Not all events, however, should trigger a full system wakeup. Instead the
2681c167b9c7SMaximilian Luz  * driver should, if necessary, inspect and forward each event to the
2682c167b9c7SMaximilian Luz  * corresponding subsystem, which in turn should decide if the system needs to
2683c167b9c7SMaximilian Luz  * be woken up. This logic has not been implemented yet, thus wakeup by this
2684c167b9c7SMaximilian Luz  * IRQ should be disabled by default to avoid spurious wake-ups, caused, for
2685c167b9c7SMaximilian Luz  * example, by the remaining battery percentage changing. Refer to comments in
2686c167b9c7SMaximilian Luz  * this function and comments in the corresponding IRQ handler for more
2687c167b9c7SMaximilian Luz  * details on how this should be implemented.
2688c167b9c7SMaximilian Luz  *
2689c167b9c7SMaximilian Luz  * See also ssam_ctrl_notif_display_off() and ssam_ctrl_notif_display_off()
2690c167b9c7SMaximilian Luz  * for functions to transition the EC into and out of the display-off state as
2691c167b9c7SMaximilian Luz  * well as more details on it.
2692c167b9c7SMaximilian Luz  *
2693c167b9c7SMaximilian Luz  * The IRQ is disabled by default and has to be enabled before it can wake up
2694c167b9c7SMaximilian Luz  * the device from suspend via ssam_irq_arm_for_wakeup(). On teardown, the IRQ
2695c167b9c7SMaximilian Luz  * should be freed via ssam_irq_free().
2696c167b9c7SMaximilian Luz  */
ssam_irq_setup(struct ssam_controller * ctrl)2697c167b9c7SMaximilian Luz int ssam_irq_setup(struct ssam_controller *ctrl)
2698c167b9c7SMaximilian Luz {
2699c167b9c7SMaximilian Luz 	struct device *dev = ssam_controller_device(ctrl);
2700c167b9c7SMaximilian Luz 	struct gpio_desc *gpiod;
2701c167b9c7SMaximilian Luz 	int irq;
2702c167b9c7SMaximilian Luz 	int status;
2703c167b9c7SMaximilian Luz 
2704c167b9c7SMaximilian Luz 	/*
2705c167b9c7SMaximilian Luz 	 * The actual GPIO interrupt is declared in ACPI as TRIGGER_HIGH.
2706c167b9c7SMaximilian Luz 	 * However, the GPIO line only gets reset by sending the GPIO callback
2707c167b9c7SMaximilian Luz 	 * command to SAM (or alternatively the display-on notification). As
2708c167b9c7SMaximilian Luz 	 * proper handling for this interrupt is not implemented yet, leaving
2709c167b9c7SMaximilian Luz 	 * the IRQ at TRIGGER_HIGH would cause an IRQ storm (as the callback
2710c167b9c7SMaximilian Luz 	 * never gets sent and thus the line never gets reset). To avoid this,
2711c167b9c7SMaximilian Luz 	 * mark the IRQ as TRIGGER_RISING for now, only creating a single
2712c167b9c7SMaximilian Luz 	 * interrupt, and let the SAM resume callback during the controller
2713c167b9c7SMaximilian Luz 	 * resume process clear it.
2714c167b9c7SMaximilian Luz 	 */
2715647e6cc9SMaximilian Luz 	const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
2716c167b9c7SMaximilian Luz 
2717c167b9c7SMaximilian Luz 	gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
2718c167b9c7SMaximilian Luz 	if (IS_ERR(gpiod))
2719c167b9c7SMaximilian Luz 		return PTR_ERR(gpiod);
2720c167b9c7SMaximilian Luz 
2721c167b9c7SMaximilian Luz 	irq = gpiod_to_irq(gpiod);
2722c167b9c7SMaximilian Luz 	gpiod_put(gpiod);
2723c167b9c7SMaximilian Luz 
2724c167b9c7SMaximilian Luz 	if (irq < 0)
2725c167b9c7SMaximilian Luz 		return irq;
2726c167b9c7SMaximilian Luz 
2727c167b9c7SMaximilian Luz 	status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf,
2728c167b9c7SMaximilian Luz 				      "ssam_wakeup", ctrl);
2729c167b9c7SMaximilian Luz 	if (status)
2730c167b9c7SMaximilian Luz 		return status;
2731c167b9c7SMaximilian Luz 
2732c167b9c7SMaximilian Luz 	ctrl->irq.num = irq;
2733c167b9c7SMaximilian Luz 	return 0;
2734c167b9c7SMaximilian Luz }
2735c167b9c7SMaximilian Luz 
2736c167b9c7SMaximilian Luz /**
2737c167b9c7SMaximilian Luz  * ssam_irq_free() - Free SAM EC wakeup-GPIO interrupt.
2738c167b9c7SMaximilian Luz  * @ctrl: The controller for which the IRQ should be freed.
2739c167b9c7SMaximilian Luz  *
2740c167b9c7SMaximilian Luz  * Free the wakeup-GPIO IRQ previously set-up via ssam_irq_setup().
2741c167b9c7SMaximilian Luz  */
ssam_irq_free(struct ssam_controller * ctrl)2742c167b9c7SMaximilian Luz void ssam_irq_free(struct ssam_controller *ctrl)
2743c167b9c7SMaximilian Luz {
2744c167b9c7SMaximilian Luz 	free_irq(ctrl->irq.num, ctrl);
2745c167b9c7SMaximilian Luz 	ctrl->irq.num = -1;
2746c167b9c7SMaximilian Luz }
2747c167b9c7SMaximilian Luz 
2748c167b9c7SMaximilian Luz /**
2749c167b9c7SMaximilian Luz  * ssam_irq_arm_for_wakeup() - Arm the EC IRQ for wakeup, if enabled.
2750c167b9c7SMaximilian Luz  * @ctrl: The controller for which the IRQ should be armed.
2751c167b9c7SMaximilian Luz  *
2752c167b9c7SMaximilian Luz  * Sets up the IRQ so that it can be used to wake the device. Specifically,
2753c167b9c7SMaximilian Luz  * this function enables the irq and then, if the device is allowed to wake up
2754c167b9c7SMaximilian Luz  * the system, calls enable_irq_wake(). See ssam_irq_disarm_wakeup() for the
2755c167b9c7SMaximilian Luz  * corresponding function to disable the IRQ.
2756c167b9c7SMaximilian Luz  *
2757c167b9c7SMaximilian Luz  * This function is intended to arm the IRQ before entering S2idle suspend.
2758c167b9c7SMaximilian Luz  *
2759c167b9c7SMaximilian Luz  * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must
2760c167b9c7SMaximilian Luz  * be balanced.
2761c167b9c7SMaximilian Luz  */
ssam_irq_arm_for_wakeup(struct ssam_controller * ctrl)2762c167b9c7SMaximilian Luz int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl)
2763c167b9c7SMaximilian Luz {
2764c167b9c7SMaximilian Luz 	struct device *dev = ssam_controller_device(ctrl);
2765c167b9c7SMaximilian Luz 	int status;
2766c167b9c7SMaximilian Luz 
2767c167b9c7SMaximilian Luz 	enable_irq(ctrl->irq.num);
2768c167b9c7SMaximilian Luz 	if (device_may_wakeup(dev)) {
2769c167b9c7SMaximilian Luz 		status = enable_irq_wake(ctrl->irq.num);
2770c167b9c7SMaximilian Luz 		if (status) {
2771c167b9c7SMaximilian Luz 			ssam_err(ctrl, "failed to enable wake IRQ: %d\n", status);
2772c167b9c7SMaximilian Luz 			disable_irq(ctrl->irq.num);
2773c167b9c7SMaximilian Luz 			return status;
2774c167b9c7SMaximilian Luz 		}
2775c167b9c7SMaximilian Luz 
2776c167b9c7SMaximilian Luz 		ctrl->irq.wakeup_enabled = true;
2777c167b9c7SMaximilian Luz 	} else {
2778c167b9c7SMaximilian Luz 		ctrl->irq.wakeup_enabled = false;
2779c167b9c7SMaximilian Luz 	}
2780c167b9c7SMaximilian Luz 
2781c167b9c7SMaximilian Luz 	return 0;
2782c167b9c7SMaximilian Luz }
2783c167b9c7SMaximilian Luz 
2784c167b9c7SMaximilian Luz /**
2785c167b9c7SMaximilian Luz  * ssam_irq_disarm_wakeup() - Disarm the wakeup IRQ.
2786c167b9c7SMaximilian Luz  * @ctrl: The controller for which the IRQ should be disarmed.
2787c167b9c7SMaximilian Luz  *
2788c167b9c7SMaximilian Luz  * Disarm the IRQ previously set up for wake via ssam_irq_arm_for_wakeup().
2789c167b9c7SMaximilian Luz  *
2790c167b9c7SMaximilian Luz  * This function is intended to disarm the IRQ after exiting S2idle suspend.
2791c167b9c7SMaximilian Luz  *
2792c167b9c7SMaximilian Luz  * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must
2793c167b9c7SMaximilian Luz  * be balanced.
2794c167b9c7SMaximilian Luz  */
ssam_irq_disarm_wakeup(struct ssam_controller * ctrl)2795c167b9c7SMaximilian Luz void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl)
2796c167b9c7SMaximilian Luz {
2797c167b9c7SMaximilian Luz 	int status;
2798c167b9c7SMaximilian Luz 
2799c167b9c7SMaximilian Luz 	if (ctrl->irq.wakeup_enabled) {
2800c167b9c7SMaximilian Luz 		status = disable_irq_wake(ctrl->irq.num);
2801c167b9c7SMaximilian Luz 		if (status)
2802c167b9c7SMaximilian Luz 			ssam_err(ctrl, "failed to disable wake IRQ: %d\n", status);
2803c167b9c7SMaximilian Luz 
2804c167b9c7SMaximilian Luz 		ctrl->irq.wakeup_enabled = false;
2805c167b9c7SMaximilian Luz 	}
2806c167b9c7SMaximilian Luz 	disable_irq(ctrl->irq.num);
2807c167b9c7SMaximilian Luz }
2808