From 0eb6584068642767baab17c2e4385a6b9c029caa Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 27 May 2022 04:34:36 +0200 Subject: platform/surface: aggregator: Allow is_ssam_device() to be used when CONFIG_SURFACE_AGGREGATOR_BUS is disabled In SSAM subsystem drivers that handle both ACPI and SSAM-native client devices, we may want to check whether we have a SSAM (native) client device. Further, we may want to do this even when instantiation thereof cannot happen due to CONFIG_SURFACE_AGGREGATOR_BUS=n. Currently, doing so causes an error due to an undefined reference error due to ssam_device_type being placed in the bus source unit. Therefore, if CONFIG_SURFACE_AGGREGATOR_BUS is not defined, simply let is_ssam_device() return false to prevent this error. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20220527023447.2460025-2-luzmaximilian@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- include/linux/surface_aggregator/device.h | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'include/linux/surface_aggregator/device.h') diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h index cc257097eb05..62b38b4487eb 100644 --- a/include/linux/surface_aggregator/device.h +++ b/include/linux/surface_aggregator/device.h @@ -177,6 +177,8 @@ struct ssam_device_driver { void (*remove)(struct ssam_device *sdev); }; +#ifdef CONFIG_SURFACE_AGGREGATOR_BUS + extern struct bus_type ssam_bus_type; extern const struct device_type ssam_device_type; @@ -193,6 +195,15 @@ static inline bool is_ssam_device(struct device *d) return d->type == &ssam_device_type; } +#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + +static inline bool is_ssam_device(struct device *d) +{ + return false; +} + +#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ + /** * to_ssam_device() - Casts the given device to a SSAM client device. * @d: The device to cast. -- cgit v1.2.3 From dc0393c76f378f68961587fd4f32de29fb8f0c79 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 27 May 2022 04:34:37 +0200 Subject: platform/surface: aggregator: Allow devices to be marked as hot-removed Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on the Surface Pro 8, can be hot-removed. When this occurs, communication with the device may fail and time out. This timeout can unnecessarily block and slow down device removal and even cause issues when the devices are detached and re-attached quickly. Thus, communication should generally be avoided once hot-removal is detected. While we already remove a device as soon as we detect its (hot-)removal, the corresponding device driver may still attempt to communicate with the device during teardown. This is especially critical as communication failure may also extend to disabling of events, which is typically done at that stage. Add a flag to allow marking devices as hot-removed. This can then be used during client driver teardown to check if any communication attempts should be avoided. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20220527023447.2460025-3-luzmaximilian@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) (limited to 'include/linux/surface_aggregator/device.h') diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h index 62b38b4487eb..6df7c8d4e50e 100644 --- a/include/linux/surface_aggregator/device.h +++ b/include/linux/surface_aggregator/device.h @@ -148,17 +148,30 @@ struct ssam_device_uid { #define SSAM_SDEV(cat, tid, iid, fun) \ SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) +/* + * enum ssam_device_flags - Flags for SSAM client devices. + * @SSAM_DEVICE_HOT_REMOVED_BIT: + * The device has been hot-removed. Further communication with it may time + * out and should be avoided. + */ +enum ssam_device_flags { + SSAM_DEVICE_HOT_REMOVED_BIT = 0, +}; + /** * struct ssam_device - SSAM client device. - * @dev: Driver model representation of the device. - * @ctrl: SSAM controller managing this device. - * @uid: UID identifying the device. + * @dev: Driver model representation of the device. + * @ctrl: SSAM controller managing this device. + * @uid: UID identifying the device. + * @flags: Device state flags, see &enum ssam_device_flags. */ struct ssam_device { struct device dev; struct ssam_controller *ctrl; struct ssam_device_uid uid; + + unsigned long flags; }; /** @@ -251,6 +264,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, int ssam_device_add(struct ssam_device *sdev); void ssam_device_remove(struct ssam_device *sdev); +/** + * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. + * @sdev: The device to mark as hot-removed. + * + * Mark the device as having been hot-removed. This signals drivers using the + * device that communication with the device should be avoided and may lead to + * timeouts. + */ +static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) +{ + dev_dbg(&sdev->dev, "marking device as hot-removed\n"); + set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); +} + +/** + * ssam_device_is_hot_removed() - Check if the given device has been + * hot-removed. + * @sdev: The device to check. + * + * Checks if the given device has been marked as hot-removed. See + * ssam_device_mark_hot_removed() for more details. + * + * Return: Returns ``true`` if the device has been marked as hot-removed. + */ +static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) +{ + return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); +} + /** * ssam_device_get() - Increment reference count of SSAM client device. * @sdev: The device to increment the reference count of. -- cgit v1.2.3 From 5c1e88b98c60e4074796e9a05d3c674479ab1919 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 27 May 2022 04:34:38 +0200 Subject: platform/surface: aggregator: Allow notifiers to avoid communication on unregistering When SSAM client devices have been (physically) hot-removed, communication attempts with those devices may fail and time out. This can even extend to event notifiers, due to which timeouts may occur during device removal, slowing down that process. Add a parameter to the notifier unregister function that allows skipping communication with the EC to prevent this. Furthermore, add wrappers for registering and unregistering notifiers belonging to SSAM client devices that automatically check if the device has been marked as hot-removed and communication should be avoided. Note that non-SSAM client devices can generally not be hot-removed, so also add a convenience wrapper for those, defaulting to allow communication. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20220527023447.2460025-4-luzmaximilian@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../driver-api/surface_aggregator/client.rst | 6 +- drivers/platform/surface/aggregator/controller.c | 53 +++++++++++------ include/linux/surface_aggregator/controller.h | 24 +++++++- include/linux/surface_aggregator/device.h | 66 ++++++++++++++++++++++ 4 files changed, 128 insertions(+), 21 deletions(-) (limited to 'include/linux/surface_aggregator/device.h') diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst index e519d374c378..27f95abdbe99 100644 --- a/Documentation/driver-api/surface_aggregator/client.rst +++ b/Documentation/driver-api/surface_aggregator/client.rst @@ -17,6 +17,8 @@ .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` +.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` +.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` @@ -312,7 +314,9 @@ Handling Events To receive events from the SAM EC, an event notifier must be registered for the desired event via |ssam_notifier_register|. The notifier must be unregistered via |ssam_notifier_unregister| once it is not required any -more. +more. For |ssam_device| type clients, the |ssam_device_notifier_register| and +|ssam_device_notifier_unregister| wrappers should be preferred as they properly +handle hot-removal of client devices. Event notifiers are registered by providing (at minimum) a callback to call in case an event has been received, the registry specifying how the event diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index b8c377b3f932..6de834b52b63 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, } /** - * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is - * no longer in use and free the corresponding entry. + * ssam_nf_refcount_disable_free() - Disable event for reference count entry if + * it is 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. + * @ec: Flag specifying if the event should actually be disabled 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. + * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the + * event is no longer requested by any client), the specified event will be + * disabled on the EC via the corresponding request. + * + * If ``ec`` equals ``false``, no request will be sent to the EC and the event + * can be considered in a detached state (i.e. no longer used but still + * enabled). Disabling an event via this method may be required for + * hot-removable devices, where event disable requests may time out after the + * device has been physically removed. + * + * In both cases, if the reference count equals zero, the corresponding + * reference count entry will be 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 @@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, * 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) + struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) { const struct ssam_event_registry reg = entry->key.reg; const struct ssam_event_id id = entry->key.id; @@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, 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); + ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + ec ? "disabling" : "detaching", reg.target_category, id.target_category, + id.instance, entry->refcount); if (entry->flags != flags) { ssam_warn(ctrl, @@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, id.instance); } - if (entry->refcount == 0) { + if (ec && entry->refcount == 0) { status = ssam_ssh_event_disable(ctrl, reg, id, flags); kfree(entry); } @@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif EXPORT_SYMBOL_GPL(ssam_notifier_register); /** - * ssam_notifier_unregister() - Unregister an event notifier. - * @ctrl: The controller the notifier has been registered on. - * @n: The event notifier to unregister. + * __ssam_notifier_unregister() - Unregister an event notifier. + * @ctrl: The controller the notifier has been registered on. + * @n: The event notifier to unregister. + * @disable: Whether to disable the corresponding event on the EC. * * Unregister an event notifier. Decrement the usage counter of the associated * SAM event if the notifier is not marked as an observer. If the usage counter - * reaches zero, the event will be disabled. + * reaches zero and ``disable`` equals ``true``, the event will be disabled. + * + * Useful for hot-removable devices, where communication may fail once the + * device has been physically removed. In that case, specifying ``disable`` as + * ``false`` avoids communication with the EC. * * Return: Returns zero on success, %-ENOENT if the given notifier block has * not been registered on the controller. If the given notifier block was the * last one associated with its specific event, returns the status of the * event-disable EC-command. */ -int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) +int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, + bool disable) { u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); struct ssam_nf_refcount_entry *entry; @@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not goto remove; } - status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); + status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); } remove: @@ -2383,7 +2400,7 @@ remove: return status; } -EXPORT_SYMBOL_GPL(ssam_notifier_unregister); +EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); /** * ssam_controller_event_enable() - Enable the specified event. @@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, return -ENOENT; } - status = ssam_nf_refcount_disable_free(ctrl, entry, flags); + status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); mutex_unlock(&nf->lock); return status; diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h index 74bfdffaf7b0..50a2b4926c06 100644 --- a/include/linux/surface_aggregator/controller.h +++ b/include/linux/surface_aggregator/controller.h @@ -835,8 +835,28 @@ struct ssam_event_notifier { int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n); -int ssam_notifier_unregister(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); +int __ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n, bool disable); + +/** + * ssam_notifier_unregister() - Unregister an event notifier. + * @ctrl: The controller the notifier has been registered on. + * @n: The event notifier to unregister. + * + * Unregister an event notifier. Decrement the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the usage counter + * reaches zero, the event will be disabled. + * + * Return: Returns zero on success, %-ENOENT if the given notifier block has + * not been registered on the controller. If the given notifier block was the + * last one associated with its specific event, returns the status of the + * event-disable EC-command. + */ +static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n) +{ + return __ssam_notifier_unregister(ctrl, n, true); +} int ssam_controller_event_enable(struct ssam_controller *ctrl, struct ssam_event_registry reg, diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h index 6df7c8d4e50e..c418f7f2732d 100644 --- a/include/linux/surface_aggregator/device.h +++ b/include/linux/surface_aggregator/device.h @@ -483,4 +483,70 @@ static inline void ssam_remove_clients(struct device *dev) {} sdev->uid.instance, ret); \ } + +/* -- Helpers for client-device notifiers. ---------------------------------- */ + +/** + * ssam_device_notifier_register() - Register an event notifier for the + * specified client device. + * @sdev: The device the notifier should be registered on. + * @n: The event notifier to register. + * + * Register an event notifier. Increment the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the event is not + * marked as an observer and is currently not enabled, it will be enabled + * during this call. If the notifier is marked as an observer, no attempt will + * be made at enabling any event and no reference count will be modified. + * + * Notifiers marked as observers do not need to be associated with one specific + * event, i.e. as long as no event matching is performed, only the event target + * category needs to be set. + * + * Return: Returns zero on success, %-ENOSPC if there have already been + * %INT_MAX notifiers for the event ID/type associated with the notifier block + * registered, %-ENOMEM if the corresponding event entry could not be + * allocated, %-ENODEV if the device is marked as hot-removed. If this is the + * first time that a notifier block is registered for the specific associated + * event, returns the status of the event-enable EC-command. + */ +static inline int ssam_device_notifier_register(struct ssam_device *sdev, + struct ssam_event_notifier *n) +{ + /* + * Note that this check does not provide any guarantees whatsoever as + * hot-removal could happen at any point and we can't protect against + * it. Nevertheless, if we can detect hot-removal, bail early to avoid + * communication timeouts. + */ + if (ssam_device_is_hot_removed(sdev)) + return -ENODEV; + + return ssam_notifier_register(sdev->ctrl, n); +} + +/** + * ssam_device_notifier_unregister() - Unregister an event notifier for the + * specified client device. + * @sdev: The device the notifier has been registered on. + * @n: The event notifier to unregister. + * + * Unregister an event notifier. Decrement the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the usage counter + * reaches zero, the event will be disabled. + * + * In case the device has been marked as hot-removed, the event will not be + * disabled on the EC, as in those cases any attempt at doing so may time out. + * + * Return: Returns zero on success, %-ENOENT if the given notifier block has + * not been registered on the controller. If the given notifier block was the + * last one associated with its specific event, returns the status of the + * event-disable EC-command. + */ +static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, + struct ssam_event_notifier *n) +{ + return __ssam_notifier_unregister(sdev->ctrl, n, + !ssam_device_is_hot_removed(sdev)); +} + #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ -- cgit v1.2.3 From eb003bf3ba221bb3d21d1fdcddaa36c158fd2d8f Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 24 Jun 2022 20:36:39 +0200 Subject: platform/surface: aggregator: Add helper macros for requests with argument and return value Add helper macros for synchronous stack-allocated Surface Aggregator request with both argument and return value, similar to the current argument-only and return-value-only ones. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20220624183642.910893-2-luzmaximilian@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- include/linux/surface_aggregator/controller.h | 125 ++++++++++++++++++++++++++ include/linux/surface_aggregator/device.h | 36 ++++++++ 2 files changed, 161 insertions(+) (limited to 'include/linux/surface_aggregator/device.h') diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h index 50a2b4926c06..d11a1c6e3186 100644 --- a/include/linux/surface_aggregator/controller.h +++ b/include/linux/surface_aggregator/controller.h @@ -469,6 +469,67 @@ struct ssam_request_spec_md { return 0; \ } +/** + * SSAM_DEFINE_SYNC_REQUEST_WR() - Define synchronous SAM request function with + * both argument and return value. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec) defining the request. + * + * Defines a function executing the synchronous SAM request specified by @spec, + * with the request taking an argument of type @atype and having a return value + * of type @rtype. The generated function takes care of setting up the request + * and response structs, buffer allocation, as well as execution of the request + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * + * The generated function is defined as ``static int name(struct + * ssam_controller *ctrl, const atype *arg, rtype *ret)``, returning the status + * of the request, which is zero on success and negative on failure. The + * ``ctrl`` parameter is the controller via which the request is sent. The + * request argument is specified via the ``arg`` pointer. The request's return + * value is written to the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_WR(name, atype, rtype, spec...) \ + static int name(struct ssam_controller *ctrl, const atype *arg, rtype *ret) \ + { \ + struct ssam_request_spec s = (struct ssam_request_spec)spec; \ + struct ssam_request rqst; \ + struct ssam_response rsp; \ + int status; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = s.target_id; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = s.instance_id; \ + rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ + rqst.length = sizeof(atype); \ + rqst.payload = (u8 *)arg; \ + \ + rsp.capacity = sizeof(rtype); \ + rsp.length = 0; \ + rsp.pointer = (u8 *)ret; \ + \ + status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ + if (status) \ + return status; \ + \ + if (rsp.length != sizeof(rtype)) { \ + struct device *dev = ssam_controller_device(ctrl); \ + dev_err(dev, \ + "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ + sizeof(rtype), rsp.length, rqst.target_category,\ + rqst.command_id); \ + return -EIO; \ + } \ + \ + return 0; \ + } + /** * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM * request function with neither argument nor return value. @@ -613,6 +674,70 @@ struct ssam_request_spec_md { return 0; \ } +/** + * SSAM_DEFINE_SYNC_REQUEST_MD_WR() - Define synchronous multi-device SAM + * request function with both argument and return value. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by @spec, + * with the request taking an argument of type @atype and having a return value + * of type @rtype. Device specifying parameters are not hard-coded, but instead + * must be provided to the function. The generated function takes care of + * setting up the request and response structs, buffer allocation, as well as + * execution of the request itself, returning once the request has been fully + * completed. The required transport buffer will be allocated on the stack. + * + * The generated function is defined as ``static int name(struct + * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg, rtype *ret)``, + * returning the status of the request, which is zero on success and negative + * on failure. The ``ctrl`` parameter is the controller via which the request + * is sent, ``tid`` the target ID for the request, and ``iid`` the instance ID. + * The request argument is specified via the ``arg`` pointer. The request's + * return value is written to the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_MD_WR(name, atype, rtype, spec...) \ + static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, \ + const atype *arg, rtype *ret) \ + { \ + struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ + struct ssam_request rqst; \ + struct ssam_response rsp; \ + int status; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = tid; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = iid; \ + rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ + rqst.length = sizeof(atype); \ + rqst.payload = (u8 *)arg; \ + \ + rsp.capacity = sizeof(rtype); \ + rsp.length = 0; \ + rsp.pointer = (u8 *)ret; \ + \ + status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ + if (status) \ + return status; \ + \ + if (rsp.length != sizeof(rtype)) { \ + struct device *dev = ssam_controller_device(ctrl); \ + dev_err(dev, \ + "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ + sizeof(rtype), rsp.length, rqst.target_category,\ + rqst.command_id); \ + return -EIO; \ + } \ + \ + return 0; \ + } + /* -- Event notifier/callbacks. --------------------------------------------- */ diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h index c418f7f2732d..6cf7e80312d5 100644 --- a/include/linux/surface_aggregator/device.h +++ b/include/linux/surface_aggregator/device.h @@ -483,6 +483,42 @@ static inline void ssam_remove_clients(struct device *dev) {} sdev->uid.instance, ret); \ } +/** + * SSAM_DEFINE_SYNC_REQUEST_CL_WR() - Define synchronous client-device SAM + * request function with argument and return value. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by @spec, + * with the request taking an argument of type @atype and having a return value + * of type @rtype. Device specifying parameters are not hard-coded, but instead + * are provided via the client device, specifically its UID, supplied when + * calling this function. The generated function takes care of setting up the + * request struct, buffer allocation, as well as execution of the request + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * + * The generated function is defined as ``static int name(struct ssam_device + * *sdev, const atype *arg, rtype *ret)``, returning the status of the request, + * which is zero on success and negative on failure. The ``sdev`` parameter + * specifies both the target device of the request and by association the + * controller via which the request is sent. The request's argument is + * specified via the ``arg`` pointer. The request's return value is written to + * the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_CL_WR(name, atype, rtype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_WR(__raw_##name, atype, rtype, spec) \ + static int name(struct ssam_device *sdev, const atype *arg, rtype *ret) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, arg, ret); \ + } + /* -- Helpers for client-device notifiers. ---------------------------------- */ -- cgit v1.2.3 From 4a4ab610b8ae912c28a4fd28442ef24ed7a1a5bd Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 24 Jun 2022 22:57:58 +0200 Subject: platform/surface: aggregator: Move device registry helper functions to core module Move helper functions for client device registration to the core module. This simplifies addition of future DT/OF support and also allows us to split out the device hub drivers into their own module. At the same time, also improve device node validation a bit by not silently skipping devices with invalid device UID specifiers. Further, ensure proper lifetime management for the firmware/software nodes associated with the added devices. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20220624205800.1355621-2-luzmaximilian@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/bus.c | 149 ++++++++++++++++++--- .../platform/surface/surface_aggregator_registry.c | 75 +---------- include/linux/surface_aggregator/device.h | 52 +++++++ 3 files changed, 187 insertions(+), 89 deletions(-) (limited to 'include/linux/surface_aggregator/device.h') diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c index abbbb5b08b07..e0b0381a2834 100644 --- a/drivers/platform/surface/aggregator/bus.c +++ b/drivers/platform/surface/aggregator/bus.c @@ -6,6 +6,7 @@ */ #include +#include #include #include @@ -14,6 +15,9 @@ #include "bus.h" #include "controller.h" + +/* -- Device and bus functions. --------------------------------------------- */ + static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -46,6 +50,7 @@ static void ssam_device_release(struct device *dev) struct ssam_device *sdev = to_ssam_device(dev); ssam_controller_put(sdev->ctrl); + fwnode_handle_put(sdev->dev.fwnode); kfree(sdev); } @@ -363,6 +368,134 @@ void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) } EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); + +/* -- Bus registration. ----------------------------------------------------- */ + +/** + * ssam_bus_register() - Register and set-up the SSAM client device bus. + */ +int ssam_bus_register(void) +{ + return bus_register(&ssam_bus_type); +} + +/** + * ssam_bus_unregister() - Unregister the SSAM client device bus. + */ +void ssam_bus_unregister(void) +{ + return bus_unregister(&ssam_bus_type); +} + + +/* -- Helpers for controller and hub devices. ------------------------------- */ + +static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid) +{ + u8 d, tc, tid, iid, fn; + int n; + + n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); + if (n != 5) + return -EINVAL; + + uid->domain = d; + uid->category = tc; + uid->target = tid; + uid->instance = iid; + uid->function = fn; + + return 0; +} + +static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid) +{ + const char *str = fwnode_get_name(node); + + /* + * To simplify definitions of firmware nodes, we set the device name + * based on the UID of the device, prefixed with "ssam:". + */ + if (strncmp(str, "ssam:", strlen("ssam:")) != 0) + return -ENODEV; + + str += strlen("ssam:"); + return ssam_device_uid_from_string(str, uid); +} + +static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node) +{ + struct ssam_device_uid uid; + struct ssam_device *sdev; + int status; + + status = ssam_get_uid_for_node(node, &uid); + if (status) + return status; + + sdev = ssam_device_alloc(ctrl, uid); + if (!sdev) + return -ENOMEM; + + sdev->dev.parent = parent; + sdev->dev.fwnode = fwnode_handle_get(node); + + status = ssam_device_add(sdev); + if (status) + ssam_device_put(sdev); + + return status; +} + +/** + * __ssam_register_clients() - Register client devices defined under the + * given firmware node as children of the given device. + * @parent: The parent device under which clients should be registered. + * @ctrl: The controller with which client should be registered. + * @node: The firmware node holding definitions of the devices to be added. + * + * Register all clients that have been defined as children of the given root + * firmware node as children of the given parent device. The respective child + * firmware nodes will be associated with the correspondingly created child + * devices. + * + * The given controller will be used to instantiate the new devices. See + * ssam_device_add() for details. + * + * Note that, generally, the use of either ssam_device_register_clients() or + * ssam_register_clients() should be preferred as they directly use the + * firmware node and/or controller associated with the given device. This + * function is only intended for use when different device specifications (e.g. + * ACPI and firmware nodes) need to be combined (as is done in the platform hub + * of the device registry). + * + * Return: Returns zero on success, nonzero on failure. + */ +int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node) +{ + struct fwnode_handle *child; + int status; + + fwnode_for_each_child_node(node, child) { + /* + * Try to add the device specified in the firmware node. If + * this fails with -ENODEV, the node does not specify any SSAM + * device, so ignore it and continue with the next one. + */ + status = ssam_add_client_device(parent, ctrl, child); + if (status && status != -ENODEV) + goto err; + } + + return 0; +err: + ssam_remove_clients(parent); + return status; +} +EXPORT_SYMBOL_GPL(__ssam_register_clients); + static int ssam_remove_device(struct device *dev, void *_data) { struct ssam_device *sdev = to_ssam_device(dev); @@ -387,19 +520,3 @@ void ssam_remove_clients(struct device *dev) device_for_each_child_reverse(dev, NULL, ssam_remove_device); } EXPORT_SYMBOL_GPL(ssam_remove_clients); - -/** - * ssam_bus_register() - Register and set-up the SSAM client device bus. - */ -int ssam_bus_register(void) -{ - return bus_register(&ssam_bus_type); -} - -/** - * ssam_bus_unregister() - Unregister the SSAM client device bus. - */ -void ssam_bus_unregister(void) -{ - return bus_unregister(&ssam_bus_type); -} diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index f1c5905f1c16..c680792a037e 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -286,76 +286,6 @@ static const struct software_node *ssam_node_group_sp8[] = { }; -/* -- Device registry helper functions. ------------------------------------- */ - -static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) -{ - u8 d, tc, tid, iid, fn; - int n; - - n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); - if (n != 5) - return -EINVAL; - - uid->domain = d; - uid->category = tc; - uid->target = tid; - uid->instance = iid; - uid->function = fn; - - return 0; -} - -static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, - struct fwnode_handle *node) -{ - struct ssam_device_uid uid; - struct ssam_device *sdev; - int status; - - status = ssam_uid_from_string(fwnode_get_name(node), &uid); - if (status) - return status; - - sdev = ssam_device_alloc(ctrl, uid); - if (!sdev) - return -ENOMEM; - - sdev->dev.parent = parent; - sdev->dev.fwnode = node; - - status = ssam_device_add(sdev); - if (status) - ssam_device_put(sdev); - - return status; -} - -static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, - struct fwnode_handle *node) -{ - struct fwnode_handle *child; - int status; - - fwnode_for_each_child_node(node, child) { - /* - * Try to add the device specified in the firmware node. If - * this fails with -EINVAL, the node does not specify any SSAM - * device, so ignore it and continue with the next one. - */ - - status = ssam_hub_add_device(parent, ctrl, child); - if (status && status != -EINVAL) - goto err; - } - - return 0; -err: - ssam_remove_clients(parent); - return status; -} - - /* -- SSAM generic subsystem hub driver framework. -------------------------- */ enum ssam_hub_state { @@ -385,7 +315,6 @@ struct ssam_hub { static void ssam_hub_update_workfn(struct work_struct *work) { struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); - struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); enum ssam_hub_state state; int status = 0; @@ -425,7 +354,7 @@ static void ssam_hub_update_workfn(struct work_struct *work) hub->state = state; if (hub->state == SSAM_HUB_CONNECTED) - status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); + status = ssam_device_register_clients(hub->sdev); else ssam_remove_clients(&hub->sdev->dev); @@ -769,7 +698,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) set_secondary_fwnode(&pdev->dev, root); - status = ssam_hub_register_clients(&pdev->dev, ctrl, root); + status = __ssam_register_clients(&pdev->dev, ctrl, root); if (status) { set_secondary_fwnode(&pdev->dev, NULL); software_node_unregister_node_group(nodes); diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h index 6cf7e80312d5..46c45d1b6368 100644 --- a/include/linux/surface_aggregator/device.h +++ b/include/linux/surface_aggregator/device.h @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -375,11 +376,62 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); /* -- Helpers for controller and hub devices. ------------------------------- */ #ifdef CONFIG_SURFACE_AGGREGATOR_BUS + +int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node); void ssam_remove_clients(struct device *dev); + #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + +static inline int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node) +{ + return 0; +} + static inline void ssam_remove_clients(struct device *dev) {} + #endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ +/** + * ssam_register_clients() - Register all client devices defined under the + * given parent device. + * @dev: The parent device under which clients should be registered. + * @ctrl: The controller with which client should be registered. + * + * Register all clients that have via firmware nodes been defined as children + * of the given (parent) device. The respective child firmware nodes will be + * associated with the correspondingly created child devices. + * + * The given controller will be used to instantiate the new devices. See + * ssam_device_add() for details. + * + * Return: Returns zero on success, nonzero on failure. + */ +static inline int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) +{ + return __ssam_register_clients(dev, ctrl, dev_fwnode(dev)); +} + +/** + * ssam_device_register_clients() - Register all client devices defined under + * the given SSAM parent device. + * @sdev: The parent device under which clients should be registered. + * + * Register all clients that have via firmware nodes been defined as children + * of the given (parent) device. The respective child firmware nodes will be + * associated with the correspondingly created child devices. + * + * The controller used by the parent device will be used to instantiate the new + * devices. See ssam_device_add() for details. + * + * Return: Returns zero on success, nonzero on failure. + */ +static inline int ssam_device_register_clients(struct ssam_device *sdev) +{ + return ssam_register_clients(&sdev->dev, sdev->ctrl); +} + /* -- Helpers for client-device requests. ----------------------------------- */ -- cgit v1.2.3