/* SPDX-License-Identifier: GPL-2.0+ */ /* * Surface System Aggregator Module (SSAM) bus and client-device subsystem. * * Main interface for the surface-aggregator bus, surface-aggregator client * devices, and respective drivers building on top of the SSAM controller. * Provides support for non-platform/non-ACPI SSAM clients via dedicated * subsystem. * * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H #define _LINUX_SURFACE_AGGREGATOR_DEVICE_H #include #include #include #include #include /* -- Surface System Aggregator Module bus. --------------------------------- */ /** * enum ssam_device_domain - SAM device domain. * @SSAM_DOMAIN_VIRTUAL: Virtual device. * @SSAM_DOMAIN_SERIALHUB: Physical device connected via Surface Serial Hub. */ enum ssam_device_domain { SSAM_DOMAIN_VIRTUAL = 0x00, SSAM_DOMAIN_SERIALHUB = 0x01, }; /** * enum ssam_virtual_tc - Target categories for the virtual SAM domain. * @SSAM_VIRTUAL_TC_HUB: Device hub category. */ enum ssam_virtual_tc { SSAM_VIRTUAL_TC_HUB = 0x00, }; /** * struct ssam_device_uid - Unique identifier for SSAM device. * @domain: Domain of the device. * @category: Target category of the device. * @target: Target ID of the device. * @instance: Instance ID of the device. * @function: Sub-function of the device. This field can be used to split a * single SAM device into multiple virtual subdevices to separate * different functionality of that device and allow one driver per * such functionality. */ struct ssam_device_uid { u8 domain; u8 category; u8 target; u8 instance; u8 function; }; /* * Special values for device matching. * * These values are intended to be used with SSAM_DEVICE(), SSAM_VDEV(), and * SSAM_SDEV() exclusively. Specifically, they are used to initialize the * match_flags member of the device ID structure. Do not use them directly * with struct ssam_device_id or struct ssam_device_uid. */ #define SSAM_SSH_TID_ANY 0xffff #define SSAM_SSH_IID_ANY 0xffff #define SSAM_SSH_FUN_ANY 0xffff /** * SSAM_DEVICE() - Initialize a &struct ssam_device_id with the given * parameters. * @d: Domain of the device. * @cat: Target category of the device. * @tid: Target ID of the device. * @iid: Instance ID of the device. * @fun: Sub-function of the device. * * Initializes a &struct ssam_device_id with the given parameters. See &struct * ssam_device_uid for details regarding the parameters. The special values * %SSAM_SSH_TID_ANY, %SSAM_SSH_IID_ANY, and %SSAM_SSH_FUN_ANY can be used to specify that * matching should ignore target ID, instance ID, and/or sub-function, * respectively. This macro initializes the ``match_flags`` field based on the * given parameters. * * Note: The parameters @d and @cat must be valid &u8 values, the parameters * @tid, @iid, and @fun must be either valid &u8 values or %SSAM_SSH_TID_ANY, * %SSAM_SSH_IID_ANY, or %SSAM_SSH_FUN_ANY, respectively. Other non-&u8 values are not * allowed. */ #define SSAM_DEVICE(d, cat, tid, iid, fun) \ .match_flags = (((tid) != SSAM_SSH_TID_ANY) ? SSAM_MATCH_TARGET : 0) \ | (((iid) != SSAM_SSH_IID_ANY) ? SSAM_MATCH_INSTANCE : 0) \ | (((fun) != SSAM_SSH_FUN_ANY) ? SSAM_MATCH_FUNCTION : 0), \ .domain = d, \ .category = cat, \ .target = __builtin_choose_expr((tid) != SSAM_SSH_TID_ANY, (tid), 0), \ .instance = __builtin_choose_expr((iid) != SSAM_SSH_IID_ANY, (iid), 0), \ .function = __builtin_choose_expr((fun) != SSAM_SSH_FUN_ANY, (fun), 0) /** * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with * the given parameters. * @cat: Target category of the device. * @tid: Target ID of the device. * @iid: Instance ID of the device. * @fun: Sub-function of the device. * * Initializes a &struct ssam_device_id with the given parameters in the * virtual domain. See &struct ssam_device_uid for details regarding the * parameters. The special values %SSAM_SSH_TID_ANY, %SSAM_SSH_IID_ANY, and * %SSAM_SSH_FUN_ANY can be used to specify that matching should ignore target ID, * instance ID, and/or sub-function, respectively. This macro initializes the * ``match_flags`` field based on the given parameters. * * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, * @iid, and @fun must be either valid &u8 values or %SSAM_SSH_TID_ANY, * %SSAM_SSH_IID_ANY, or %SSAM_SSH_FUN_ANY, respectively. Other non-&u8 values are not * allowed. */ #define SSAM_VDEV(cat, tid, iid, fun) \ SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, SSAM_SSH_TID_##tid, iid, fun) /** * SSAM_SDEV() - Initialize a &struct ssam_device_id as physical SSH device * with the given parameters. * @cat: Target category of the device. * @tid: Target ID of the device. * @iid: Instance ID of the device. * @fun: Sub-function of the device. * * Initializes a &struct ssam_device_id with the given parameters in the SSH * domain. See &struct ssam_device_uid for details regarding the parameters. * The special values %SSAM_SSH_TID_ANY, %SSAM_SSH_IID_ANY, and * %SSAM_SSH_FUN_ANY can be used to specify that matching should ignore target * ID, instance ID, and/or sub-function, respectively. This macro initializes * the ``match_flags`` field based on the given parameters. * * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, * @iid, and @fun must be either valid &u8 values or %SSAM_SSH_TID_ANY, * %SSAM_SSH_IID_ANY, or %SSAM_SSH_FUN_ANY, respectively. Other non-&u8 values * are not allowed. */ #define SSAM_SDEV(cat, tid, iid, fun) \ SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, SSAM_SSH_TID_##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. * @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; }; /** * struct ssam_device_driver - SSAM client device driver. * @driver: Base driver model structure. * @match_table: Match table specifying which devices the driver should bind to. * @probe: Called when the driver is being bound to a device. * @remove: Called when the driver is being unbound from the device. */ struct ssam_device_driver { struct device_driver driver; const struct ssam_device_id *match_table; int (*probe)(struct ssam_device *sdev); void (*remove)(struct ssam_device *sdev); }; #ifdef CONFIG_SURFACE_AGGREGATOR_BUS extern const struct device_type ssam_device_type; /** * is_ssam_device() - Check if the given device is a SSAM client device. * @d: The device to test the type of. * * Return: Returns %true if the specified device is of type &struct * ssam_device, i.e. the device type points to %ssam_device_type, and %false * otherwise. */ 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. * * Casts the given &struct device to a &struct ssam_device. The caller has to * ensure that the given device is actually enclosed in a &struct ssam_device, * e.g. by calling is_ssam_device(). * * Return: Returns a pointer to the &struct ssam_device wrapping the given * device @d. */ #define to_ssam_device(d) container_of_const(d, struct ssam_device, dev) /** * to_ssam_device_driver() - Casts the given device driver to a SSAM client * device driver. * @d: The driver to cast. * * Casts the given &struct device_driver to a &struct ssam_device_driver. The * caller has to ensure that the given driver is actually enclosed in a * &struct ssam_device_driver. * * Return: Returns the pointer to the &struct ssam_device_driver wrapping the * given device driver @d. */ #define to_ssam_device_driver(d) container_of_const(d, struct ssam_device_driver, driver) const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, const struct ssam_device_uid uid); const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev); const void *ssam_device_get_match_data(const struct ssam_device *dev); struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, struct ssam_device_uid uid); 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. * * Increments the reference count of the given SSAM client device by * incrementing the reference count of the enclosed &struct device via * get_device(). * * See ssam_device_put() for the counter-part of this function. * * Return: Returns the device provided as input. */ static inline struct ssam_device *ssam_device_get(struct ssam_device *sdev) { return sdev ? to_ssam_device(get_device(&sdev->dev)) : NULL; } /** * ssam_device_put() - Decrement reference count of SSAM client device. * @sdev: The device to decrement the reference count of. * * Decrements the reference count of the given SSAM client device by * decrementing the reference count of the enclosed &struct device via * put_device(). * * See ssam_device_get() for the counter-part of this function. */ static inline void ssam_device_put(struct ssam_device *sdev) { if (sdev) put_device(&sdev->dev); } /** * ssam_device_get_drvdata() - Get driver-data of SSAM client device. * @sdev: The device to get the driver-data from. * * Return: Returns the driver-data of the given device, previously set via * ssam_device_set_drvdata(). */ static inline void *ssam_device_get_drvdata(struct ssam_device *sdev) { return dev_get_drvdata(&sdev->dev); } /** * ssam_device_set_drvdata() - Set driver-data of SSAM client device. * @sdev: The device to set the driver-data of. * @data: The data to set the device's driver-data pointer to. */ static inline void ssam_device_set_drvdata(struct ssam_device *sdev, void *data) { dev_set_drvdata(&sdev->dev, data); } int __ssam_device_driver_register(struct ssam_device_driver *d, struct module *o); void ssam_device_driver_unregister(struct ssam_device_driver *d); /** * ssam_device_driver_register() - Register a SSAM client device driver. * @drv: The driver to register. */ #define ssam_device_driver_register(drv) \ __ssam_device_driver_register(drv, THIS_MODULE) /** * module_ssam_device_driver() - Helper macro for SSAM device driver * registration. * @drv: The driver managed by this module. * * Helper macro to register a SSAM device driver via module_init() and * module_exit(). This macro may only be used once per module and replaces the * aforementioned definitions. */ #define module_ssam_device_driver(drv) \ module_driver(drv, ssam_device_driver_register, \ ssam_device_driver_unregister) /* -- 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. ----------------------------------- */ /** * SSAM_DEFINE_SYNC_REQUEST_CL_N() - Define synchronous client-device SAM * request function with neither argument nor return value. * @name: Name of the generated function. * @spec: Specification (&struct ssam_request_spec_md) defining the request. * * Defines a function executing the synchronous SAM request specified by * @spec, with the request having neither argument nor return value. 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)``, 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. * * Refer to ssam_request_do_sync_onstack() for more details on the behavior of * the generated function. */ #define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ static int name(struct ssam_device *sdev) \ { \ return __raw_##name(sdev->ctrl, sdev->uid.target, \ sdev->uid.instance); \ } /** * SSAM_DEFINE_SYNC_REQUEST_CL_W() - Define synchronous client-device SAM * request function with argument. * @name: Name of the generated function. * @atype: Type of the request's argument. * @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 no * return value. 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)``, 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. * * Refer to ssam_request_do_sync_onstack() for more details on the behavior of * the generated function. */ #define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ static int name(struct ssam_device *sdev, const atype *arg) \ { \ return __raw_##name(sdev->ctrl, sdev->uid.target, \ sdev->uid.instance, arg); \ } /** * SSAM_DEFINE_SYNC_REQUEST_CL_R() - Define synchronous client-device SAM * request function with return value. * @name: Name of the generated function. * @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 no argument but 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, 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 return value is written to the memory * pointed to by the ``ret`` parameter. * * Refer to ssam_request_do_sync_onstack() for more details on the behavior of * the generated function. */ #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ static int name(struct ssam_device *sdev, rtype *ret) \ { \ return __raw_##name(sdev->ctrl, sdev->uid.target, \ 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_do_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. ---------------------------------- */ /** * 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 */