Re: [PATCH v2 2/7] platform/surface: aggregator: Allow enabling of events without notifiers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

On 6/4/21 3:47 PM, Maximilian Luz wrote:
> We can already enable and disable SAM events via one of two ways: either
> via a (non-observer) notifier tied to a specific event group, or a
> generic event enable/disable request. In some instances, however,
> neither method may be desirable.
> 
> The first method will tie the event enable request to a specific
> notifier, however, when we want to receive notifications for multiple
> event groups of the same target category and forward this to the same
> notifier callback, we may receive duplicate events, i.e. one event per
> registered notifier. The second method will bypass the internal
> reference counting mechanism, meaning that a disable request will
> disable the event regardless of any other client driver using it, which
> may break the functionality of that driver.
> 
> To address this problem, add new functions that allow enabling and
> disabling of events via the event reference counting mechanism built
> into the controller, without needing to register a notifier.
> 
> This can then be used in combination with observer notifiers to process
> multiple events of the same target category without duplication in the
> same callback function.
> 
> Signed-off-by: Maximilian Luz <luzmaximilian@xxxxxxxxx>
> ---
> 
> Changes in v2:
>  - Remove unused variable
>  - Avoid code duplication
> 
> ---
>  .../platform/surface/aggregator/controller.c  | 293 +++++++++++++++---
>  include/linux/surface_aggregator/controller.h |   8 +
>  2 files changed, 253 insertions(+), 48 deletions(-)
> 
> diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
> index cd3a6b77f48d..c673aa8309c8 100644
> --- a/drivers/platform/surface/aggregator/controller.c
> +++ b/drivers/platform/surface/aggregator/controller.c
> @@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg,
>  	return NULL;
>  }
>  
> +/**
> + * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the
> + * given event and free its entry if the reference count reaches zero.
> + * @nf:  The notifier system reference.
> + * @reg: The registry used to enable/disable the event.
> + * @id:  The event ID.
> + *
> + * Decrements the reference-/activation-count of the specified event, freeing
> + * its entry if it reaches zero.
> + *
> + * Note: ``nf->lock`` must be held when calling this function.
> + */
> +static void ssam_nf_refcount_dec_free(struct ssam_nf *nf,
> +				      struct ssam_event_registry reg,
> +				      struct ssam_event_id id)
> +{
> +	struct ssam_nf_refcount_entry *entry;
> +
> +	lockdep_assert_held(&nf->lock);
> +
> +	entry = ssam_nf_refcount_dec(nf, reg, id);
> +	if (entry && entry->refcount == 0)
> +		kfree(entry);
> +}
> +
>  /**
>   * ssam_nf_refcount_empty() - Test if the notification system has any
>   * enabled/active events.
> @@ -2122,6 +2147,109 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
>  
>  /* -- Top-level event registry interface. ----------------------------------- */
>  
> +/**
> + * ssam_nf_refcount_enable() - Enable event for reference count entry if it has
> + * not already been enabled.
> + * @ctrl:  The controller to enable the event on.
> + * @entry: The reference count entry for the event to be enabled.
> + * @flags: The flags used for enabling the event on the EC.
> + *
> + * Enable the event associated with the given reference count entry if the
> + * reference count equals one, i.e. the event has not previously been enabled.
> + * If the event has already been enabled (i.e. reference count not equal to
> + * one), check that the flags used for enabling match and warn about this if
> + * they do not.
> + *
> + * This does not modify the reference count itself, which is done with
> + * ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
> + *
> + * Note: ``nf->lock`` must be held when calling this function.
> + *
> + * Return: Returns zero on success. If the event is enabled by this call,
> + * returns the status of the event-enable EC command.
> + */
> +static int ssam_nf_refcount_enable(struct ssam_controller *ctrl,
> +				   struct ssam_nf_refcount_entry *entry, u8 flags)
> +{
> +	const struct ssam_event_registry reg = entry->key.reg;
> +	const struct ssam_event_id id = entry->key.id;
> +	struct ssam_nf *nf = &ctrl->cplt.event.notif;
> +	int status;
> +
> +	lockdep_assert_held(&nf->lock);
> +
> +	ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
> +		 reg.target_category, id.target_category, id.instance, entry->refcount);
> +
> +	if (entry->refcount == 1) {
> +		status = ssam_ssh_event_enable(ctrl, reg, id, flags);
> +		if (status)
> +			return status;
> +
> +		entry->flags = flags;
> +
> +	} else if (entry->flags != flags) {
> +		ssam_warn(ctrl,
> +			  "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
> +			  flags, entry->flags, reg.target_category, id.target_category,
> +			  id.instance);
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * ssam_nf_refcount_enable() - Disable event for reference count entry if it is

s/ssam_nf_refcount_enable/ssam_nf_refcount_disable_free/

No need to resend, I'll fix this up when merging this series.

Regards,

Hans




> + * no longer in use and free the corresponding entry.
> + * @ctrl:  The controller to disable the event on.
> + * @entry: The reference count entry for the event to be disabled.
> + * @flags: The flags used for enabling the event on the EC.
> + *
> + * If the reference count equals zero, i.e. the event is no longer requested by
> + * any client, the event will be disabled and the corresponding reference count
> + * entry freed. The reference count entry must not be used any more after a
> + * call to this function.
> + *
> + * Also checks if the flags used for disabling the event match the flags used
> + * for enabling the event and warns if they do not (regardless of reference
> + * count).
> + *
> + * This does not modify the reference count itself, which is done with
> + * ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
> + *
> + * Note: ``nf->lock`` must be held when calling this function.
> + *
> + * Return: Returns zero on success. If the event is disabled by this call,
> + * returns the status of the event-enable EC command.
> + */
> +static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
> +					 struct ssam_nf_refcount_entry *entry, u8 flags)
> +{
> +	const struct ssam_event_registry reg = entry->key.reg;
> +	const struct ssam_event_id id = entry->key.id;
> +	struct ssam_nf *nf = &ctrl->cplt.event.notif;
> +	int status;
> +
> +	lockdep_assert_held(&nf->lock);
> +
> +	ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
> +		 reg.target_category, id.target_category, id.instance, entry->refcount);
> +
> +	if (entry->flags != flags) {
> +		ssam_warn(ctrl,
> +			  "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
> +			  flags, entry->flags, reg.target_category, id.target_category,
> +			  id.instance);
> +	}
> +
> +	if (entry->refcount == 0) {
> +		status = ssam_ssh_event_disable(ctrl, reg, id, flags);
> +		kfree(entry);
> +	}
> +
> +	return status;
> +}
> +
>  /**
>   * ssam_notifier_register() - Register an event notifier.
>   * @ctrl: The controller to register the notifier on.
> @@ -2166,41 +2294,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif
>  			mutex_unlock(&nf->lock);
>  			return PTR_ERR(entry);
>  		}
> -
> -		ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
> -			 n->event.reg.target_category, n->event.id.target_category,
> -			 n->event.id.instance, entry->refcount);
>  	}
>  
>  	status = ssam_nfblk_insert(nf_head, &n->base);
>  	if (status) {
> -		if (entry) {
> -			entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
> -			if (entry->refcount == 0)
> -				kfree(entry);
> -		}
> +		if (entry)
> +			ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
>  
>  		mutex_unlock(&nf->lock);
>  		return status;
>  	}
>  
> -	if (entry && entry->refcount == 1) {
> -		status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags);
> +	if (entry) {
> +		status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags);
>  		if (status) {
>  			ssam_nfblk_remove(&n->base);
> -			kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id));
> +			ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
>  			mutex_unlock(&nf->lock);
>  			synchronize_srcu(&nf_head->srcu);
>  			return status;
>  		}
> -
> -		entry->flags = n->event.flags;
> -
> -	} else if (entry && entry->flags != n->event.flags) {
> -		ssam_warn(ctrl,
> -			  "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
> -			  n->event.flags, entry->flags, n->event.reg.target_category,
> -			  n->event.id.target_category, n->event.id.instance);
>  	}
>  
>  	mutex_unlock(&nf->lock);
> @@ -2247,35 +2360,20 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
>  	 * If this is an observer notifier, do not attempt to disable the
>  	 * event, just remove it.
>  	 */
> -	if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)
> -		goto remove;
> -
> -	entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
> -	if (WARN_ON(!entry)) {
> -		/*
> -		 * If this does not return an entry, there's a logic error
> -		 * somewhere: The notifier block is registered, but the event
> -		 * refcount entry is not there. Remove the notifier block
> -		 * anyways.
> -		 */
> -		status = -ENOENT;
> -		goto remove;
> -	}
> -
> -	ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
> -		 n->event.reg.target_category, n->event.id.target_category,
> -		 n->event.id.instance, entry->refcount);
> -
> -	if (entry->flags != n->event.flags) {
> -		ssam_warn(ctrl,
> -			  "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
> -			  n->event.flags, entry->flags, n->event.reg.target_category,
> -			  n->event.id.target_category, n->event.id.instance);
> -	}
> +	if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
> +		entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
> +		if (WARN_ON(!entry)) {
> +			/*
> +			 * If this does not return an entry, there's a logic
> +			 * error somewhere: The notifier block is registered,
> +			 * but the event refcount entry is not there. Remove
> +			 * the notifier block anyways.
> +			 */
> +			status = -ENOENT;
> +			goto remove;
> +		}
>  
> -	if (entry->refcount == 0) {
> -		status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags);
> -		kfree(entry);
> +		status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags);
>  	}
>  
>  remove:
> @@ -2287,6 +2385,105 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
>  }
>  EXPORT_SYMBOL_GPL(ssam_notifier_unregister);
>  
> +/**
> + * ssam_controller_event_enable() - Enable the specified event.
> + * @ctrl:  The controller to enable the event for.
> + * @reg:   The event registry to use for enabling the event.
> + * @id:    The event ID specifying the event to be enabled.
> + * @flags: The SAM event flags used for enabling the event.
> + *
> + * Increment the event reference count of the specified event. If the event has
> + * not been enabled previously, it will be enabled by this call.
> + *
> + * Note: In general, ssam_notifier_register() with a non-observer notifier
> + * should be preferred for enabling/disabling events, as this will guarantee
> + * proper ordering and event forwarding in case of errors during event
> + * enabling/disabling.
> + *
> + * Return: Returns zero on success, %-ENOSPC if the reference count for the
> + * specified event has reached its maximum, %-ENOMEM if the corresponding event
> + * entry could not be allocated. If this is the first time that this event has
> + * been enabled (i.e. the reference count was incremented from zero to one by
> + * this call), returns the status of the event-enable EC-command.
> + */
> +int ssam_controller_event_enable(struct ssam_controller *ctrl,
> +				 struct ssam_event_registry reg,
> +				 struct ssam_event_id id, u8 flags)
> +{
> +	u16 rqid = ssh_tc_to_rqid(id.target_category);
> +	struct ssam_nf *nf = &ctrl->cplt.event.notif;
> +	struct ssam_nf_refcount_entry *entry;
> +	int status;
> +
> +	if (!ssh_rqid_is_event(rqid))
> +		return -EINVAL;
> +
> +	mutex_lock(&nf->lock);
> +
> +	entry = ssam_nf_refcount_inc(nf, reg, id);
> +	if (IS_ERR(entry)) {
> +		mutex_unlock(&nf->lock);
> +		return PTR_ERR(entry);
> +	}
> +
> +	status = ssam_nf_refcount_enable(ctrl, entry, flags);
> +	if (status) {
> +		ssam_nf_refcount_dec_free(nf, reg, id);
> +		mutex_unlock(&nf->lock);
> +		return status;
> +	}
> +
> +	mutex_unlock(&nf->lock);
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(ssam_controller_event_enable);
> +
> +/**
> + * ssam_controller_event_disable() - Disable the specified event.
> + * @ctrl:  The controller to disable the event for.
> + * @reg:   The event registry to use for disabling the event.
> + * @id:    The event ID specifying the event to be disabled.
> + * @flags: The flags used when enabling the event.
> + *
> + * Decrement the reference count of the specified event. If the reference count
> + * reaches zero, the event will be disabled.
> + *
> + * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a
> + * non-observer notifier should be preferred for enabling/disabling events, as
> + * this will guarantee proper ordering and event forwarding in case of errors
> + * during event enabling/disabling.
> + *
> + * Return: Returns zero on success, %-ENOENT if the given event has not been
> + * enabled on the controller. If the reference count of the event reaches zero
> + * during this call, returns the status of the event-disable EC-command.
> + */
> +int ssam_controller_event_disable(struct ssam_controller *ctrl,
> +				  struct ssam_event_registry reg,
> +				  struct ssam_event_id id, u8 flags)
> +{
> +	u16 rqid = ssh_tc_to_rqid(id.target_category);
> +	struct ssam_nf *nf = &ctrl->cplt.event.notif;
> +	struct ssam_nf_refcount_entry *entry;
> +	int status = 0;
> +
> +	if (!ssh_rqid_is_event(rqid))
> +		return -EINVAL;
> +
> +	mutex_lock(&nf->lock);
> +
> +	entry = ssam_nf_refcount_dec(nf, reg, id);
> +	if (!entry) {
> +		mutex_unlock(&nf->lock);
> +		return -ENOENT;
> +	}
> +
> +	status = ssam_nf_refcount_disable_free(ctrl, entry, flags);
> +
> +	mutex_unlock(&nf->lock);
> +	return status;
> +}
> +EXPORT_SYMBOL_GPL(ssam_controller_event_disable);
> +
>  /**
>   * ssam_notifier_disable_registered() - Disable events for all registered
>   * notifiers.
> diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
> index cf4bb48a850e..7965bdc669c5 100644
> --- a/include/linux/surface_aggregator/controller.h
> +++ b/include/linux/surface_aggregator/controller.h
> @@ -838,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
>  int ssam_notifier_unregister(struct ssam_controller *ctrl,
>  			     struct ssam_event_notifier *n);
>  
> +int ssam_controller_event_enable(struct ssam_controller *ctrl,
> +				 struct ssam_event_registry reg,
> +				 struct ssam_event_id id, u8 flags);
> +
> +int ssam_controller_event_disable(struct ssam_controller *ctrl,
> +				  struct ssam_event_registry reg,
> +				  struct ssam_event_id id, u8 flags);
> +
>  #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */
> 




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux