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

Lines 1–500 of 2578.