diff options
Diffstat (limited to 'drivers/gpu/drm/drm_privacy_screen.c')
| -rw-r--r-- | drivers/gpu/drm/drm_privacy_screen.c | 467 | 
1 files changed, 467 insertions, 0 deletions
| diff --git a/drivers/gpu/drm/drm_privacy_screen.c b/drivers/gpu/drm/drm_privacy_screen.c new file mode 100644 index 000000000000..beaf99e9120a --- /dev/null +++ b/drivers/gpu/drm/drm_privacy_screen.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2020 - 2021 Red Hat, Inc. + * + * Authors: + * Hans de Goede <[email protected]> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <drm/drm_privacy_screen_machine.h> +#include <drm/drm_privacy_screen_consumer.h> +#include <drm/drm_privacy_screen_driver.h> +#include "drm_internal.h" + +/** + * DOC: overview + * + * This class allows non KMS drivers, from e.g. drivers/platform/x86 to + * register a privacy-screen device, which the KMS drivers can then use + * to implement the standard privacy-screen properties, see + * :ref:`Standard Connector Properties<standard_connector_properties>`. + * + * KMS drivers using a privacy-screen class device are advised to use the + * drm_connector_attach_privacy_screen_provider() and + * drm_connector_update_privacy_screen() helpers for dealing with this. + */ + +#define to_drm_privacy_screen(dev) \ +	container_of(dev, struct drm_privacy_screen, dev) + +static DEFINE_MUTEX(drm_privacy_screen_lookup_lock); +static LIST_HEAD(drm_privacy_screen_lookup_list); + +static DEFINE_MUTEX(drm_privacy_screen_devs_lock); +static LIST_HEAD(drm_privacy_screen_devs); + +/*** drm_privacy_screen_machine.h functions ***/ + +/** + * drm_privacy_screen_lookup_add - add an entry to the static privacy-screen + *    lookup list + * @lookup: lookup list entry to add + * + * Add an entry to the static privacy-screen lookup list. Note the + * &struct list_head which is part of the &struct drm_privacy_screen_lookup + * gets added to a list owned by the privacy-screen core. So the passed in + * &struct drm_privacy_screen_lookup must not be free-ed until it is removed + * from the lookup list by calling drm_privacy_screen_lookup_remove(). + */ +void drm_privacy_screen_lookup_add(struct drm_privacy_screen_lookup *lookup) +{ +	mutex_lock(&drm_privacy_screen_lookup_lock); +	list_add(&lookup->list, &drm_privacy_screen_lookup_list); +	mutex_unlock(&drm_privacy_screen_lookup_lock); +} +EXPORT_SYMBOL(drm_privacy_screen_lookup_add); + +/** + * drm_privacy_screen_lookup_remove - remove an entry to the static + *    privacy-screen lookup list + * @lookup: lookup list entry to remove + * + * Remove an entry previously added with drm_privacy_screen_lookup_add() + * from the static privacy-screen lookup list. + */ +void drm_privacy_screen_lookup_remove(struct drm_privacy_screen_lookup *lookup) +{ +	mutex_lock(&drm_privacy_screen_lookup_lock); +	list_del(&lookup->list); +	mutex_unlock(&drm_privacy_screen_lookup_lock); +} +EXPORT_SYMBOL(drm_privacy_screen_lookup_remove); + +/*** drm_privacy_screen_consumer.h functions ***/ + +static struct drm_privacy_screen *drm_privacy_screen_get_by_name( +	const char *name) +{ +	struct drm_privacy_screen *priv; +	struct device *dev = NULL; + +	mutex_lock(&drm_privacy_screen_devs_lock); + +	list_for_each_entry(priv, &drm_privacy_screen_devs, list) { +		if (strcmp(dev_name(&priv->dev), name) == 0) { +			dev = get_device(&priv->dev); +			break; +		} +	} + +	mutex_unlock(&drm_privacy_screen_devs_lock); + +	return dev ? to_drm_privacy_screen(dev) : NULL; +} + +/** + * drm_privacy_screen_get - get a privacy-screen provider + * @dev: consumer-device for which to get a privacy-screen provider + * @con_id: (video)connector name for which to get a privacy-screen provider + * + * Get a privacy-screen provider for a privacy-screen attached to the + * display described by the @dev and @con_id parameters. + * + * Return: + * * A pointer to a &struct drm_privacy_screen on success. + * * ERR_PTR(-ENODEV) if no matching privacy-screen is found + * * ERR_PTR(-EPROBE_DEFER) if there is a matching privacy-screen, + *                          but it has not been registered yet. + */ +struct drm_privacy_screen *drm_privacy_screen_get(struct device *dev, +						  const char *con_id) +{ +	const char *dev_id = dev ? dev_name(dev) : NULL; +	struct drm_privacy_screen_lookup *l; +	struct drm_privacy_screen *priv; +	const char *provider = NULL; +	int match, best = -1; + +	/* +	 * For now we only support using a static lookup table, which is +	 * populated by the drm_privacy_screen_arch_init() call. This should +	 * be extended with device-tree / fw_node lookup when support is added +	 * for device-tree using hardware with a privacy-screen. +	 * +	 * The lookup algorithm was shamelessly taken from the clock +	 * framework: +	 * +	 * We do slightly fuzzy matching here: +	 *  An entry with a NULL ID is assumed to be a wildcard. +	 *  If an entry has a device ID, it must match +	 *  If an entry has a connection ID, it must match +	 * Then we take the most specific entry - with the following order +	 * of precedence: dev+con > dev only > con only. +	 */ +	mutex_lock(&drm_privacy_screen_lookup_lock); + +	list_for_each_entry(l, &drm_privacy_screen_lookup_list, list) { +		match = 0; + +		if (l->dev_id) { +			if (!dev_id || strcmp(l->dev_id, dev_id)) +				continue; + +			match += 2; +		} + +		if (l->con_id) { +			if (!con_id || strcmp(l->con_id, con_id)) +				continue; + +			match += 1; +		} + +		if (match > best) { +			provider = l->provider; +			best = match; +		} +	} + +	mutex_unlock(&drm_privacy_screen_lookup_lock); + +	if (!provider) +		return ERR_PTR(-ENODEV); + +	priv = drm_privacy_screen_get_by_name(provider); +	if (!priv) +		return ERR_PTR(-EPROBE_DEFER); + +	return priv; +} +EXPORT_SYMBOL(drm_privacy_screen_get); + +/** + * drm_privacy_screen_put - release a privacy-screen reference + * @priv: privacy screen reference to release + * + * Release a privacy-screen provider reference gotten through + * drm_privacy_screen_get(). May be called with a NULL or ERR_PTR, + * in which case it is a no-op. + */ +void drm_privacy_screen_put(struct drm_privacy_screen *priv) +{ +	if (IS_ERR_OR_NULL(priv)) +		return; + +	put_device(&priv->dev); +} +EXPORT_SYMBOL(drm_privacy_screen_put); + +/** + * drm_privacy_screen_set_sw_state - set a privacy-screen's sw-state + * @priv: privacy screen to set the sw-state for + * @sw_state: new sw-state value to set + * + * Set the sw-state of a privacy screen. If the privacy-screen is not + * in a locked hw-state, then the actual and hw-state of the privacy-screen + * will be immediately updated to the new value. If the privacy-screen is + * in a locked hw-state, then the new sw-state will be remembered as the + * requested state to put the privacy-screen in when it becomes unlocked. + * + * Return: 0 on success, negative error code on failure. + */ +int drm_privacy_screen_set_sw_state(struct drm_privacy_screen *priv, +				    enum drm_privacy_screen_status sw_state) +{ +	int ret = 0; + +	mutex_lock(&priv->lock); + +	if (!priv->ops) { +		ret = -ENODEV; +		goto out; +	} + +	/* +	 * As per the DRM connector properties documentation, setting the +	 * sw_state while the hw_state is locked is allowed. In this case +	 * it is a no-op other then storing the new sw_state so that it +	 * can be honored when the state gets unlocked. +	 * Also skip the set if the hw already is in the desired state. +	 */ +	if (priv->hw_state >= PRIVACY_SCREEN_DISABLED_LOCKED || +	    priv->hw_state == sw_state) { +		priv->sw_state = sw_state; +		goto out; +	} + +	ret = priv->ops->set_sw_state(priv, sw_state); +out: +	mutex_unlock(&priv->lock); +	return ret; +} +EXPORT_SYMBOL(drm_privacy_screen_set_sw_state); + +/** + * drm_privacy_screen_get_state - get privacy-screen's current state + * @priv: privacy screen to get the state for + * @sw_state_ret: address where to store the privacy-screens current sw-state + * @hw_state_ret: address where to store the privacy-screens current hw-state + * + * Get the current state of a privacy-screen, both the sw-state and the + * hw-state. + */ +void drm_privacy_screen_get_state(struct drm_privacy_screen *priv, +				  enum drm_privacy_screen_status *sw_state_ret, +				  enum drm_privacy_screen_status *hw_state_ret) +{ +	mutex_lock(&priv->lock); +	*sw_state_ret = priv->sw_state; +	*hw_state_ret = priv->hw_state; +	mutex_unlock(&priv->lock); +} +EXPORT_SYMBOL(drm_privacy_screen_get_state); + +/** + * drm_privacy_screen_register_notifier - register a notifier + * @priv: Privacy screen to register the notifier with + * @nb: Notifier-block for the notifier to register + * + * Register a notifier with the privacy-screen to be notified of changes made + * to the privacy-screen state from outside of the privacy-screen class. + * E.g. the state may be changed by the hardware itself in response to a + * hotkey press. + * + * The notifier is called with no locks held. The new hw_state and sw_state + * can be retrieved using the drm_privacy_screen_get_state() function. + * A pointer to the drm_privacy_screen's struct is passed as the void *data + * argument of the notifier_block's notifier_call. + * + * The notifier will NOT be called when changes are made through + * drm_privacy_screen_set_sw_state(). It is only called for external changes. + * + * Return: 0 on success, negative error code on failure. + */ +int drm_privacy_screen_register_notifier(struct drm_privacy_screen *priv, +					 struct notifier_block *nb) +{ +	return blocking_notifier_chain_register(&priv->notifier_head, nb); +} +EXPORT_SYMBOL(drm_privacy_screen_register_notifier); + +/** + * drm_privacy_screen_unregister_notifier - unregister a notifier + * @priv: Privacy screen to register the notifier with + * @nb: Notifier-block for the notifier to register + * + * Unregister a notifier registered with drm_privacy_screen_register_notifier(). + * + * Return: 0 on success, negative error code on failure. + */ +int drm_privacy_screen_unregister_notifier(struct drm_privacy_screen *priv, +					   struct notifier_block *nb) +{ +	return blocking_notifier_chain_unregister(&priv->notifier_head, nb); +} +EXPORT_SYMBOL(drm_privacy_screen_unregister_notifier); + +/*** drm_privacy_screen_driver.h functions ***/ + +static ssize_t sw_state_show(struct device *dev, +			     struct device_attribute *attr, char *buf) +{ +	struct drm_privacy_screen *priv = to_drm_privacy_screen(dev); +	const char * const sw_state_names[] = { +		"Disabled", +		"Enabled", +	}; +	ssize_t ret; + +	mutex_lock(&priv->lock); + +	if (!priv->ops) +		ret = -ENODEV; +	else if (WARN_ON(priv->sw_state >= ARRAY_SIZE(sw_state_names))) +		ret = -ENXIO; +	else +		ret = sprintf(buf, "%s\n", sw_state_names[priv->sw_state]); + +	mutex_unlock(&priv->lock); +	return ret; +} +/* + * RO: Do not allow setting the sw_state through sysfs, this MUST be done + * through the drm_properties on the drm_connector. + */ +static DEVICE_ATTR_RO(sw_state); + +static ssize_t hw_state_show(struct device *dev, +			     struct device_attribute *attr, char *buf) +{ +	struct drm_privacy_screen *priv = to_drm_privacy_screen(dev); +	const char * const hw_state_names[] = { +		"Disabled", +		"Enabled", +		"Disabled, locked", +		"Enabled, locked", +	}; +	ssize_t ret; + +	mutex_lock(&priv->lock); + +	if (!priv->ops) +		ret = -ENODEV; +	else if (WARN_ON(priv->hw_state >= ARRAY_SIZE(hw_state_names))) +		ret = -ENXIO; +	else +		ret = sprintf(buf, "%s\n", hw_state_names[priv->hw_state]); + +	mutex_unlock(&priv->lock); +	return ret; +} +static DEVICE_ATTR_RO(hw_state); + +static struct attribute *drm_privacy_screen_attrs[] = { +	&dev_attr_sw_state.attr, +	&dev_attr_hw_state.attr, +	NULL +}; +ATTRIBUTE_GROUPS(drm_privacy_screen); + +static struct device_type drm_privacy_screen_type = { +	.name = "privacy_screen", +	.groups = drm_privacy_screen_groups, +}; + +static void drm_privacy_screen_device_release(struct device *dev) +{ +	struct drm_privacy_screen *priv = to_drm_privacy_screen(dev); + +	kfree(priv); +} + +/** + * drm_privacy_screen_register - register a privacy-screen + * @parent: parent-device for the privacy-screen + * @ops: &struct drm_privacy_screen_ops pointer with ops for the privacy-screen + * + * Create and register a privacy-screen. + * + * Return: + * * A pointer to the created privacy-screen on success. + * * An ERR_PTR(errno) on failure. + */ +struct drm_privacy_screen *drm_privacy_screen_register( +	struct device *parent, const struct drm_privacy_screen_ops *ops) +{ +	struct drm_privacy_screen *priv; +	int ret; + +	priv = kzalloc(sizeof(*priv), GFP_KERNEL); +	if (!priv) +		return ERR_PTR(-ENOMEM); + +	mutex_init(&priv->lock); +	BLOCKING_INIT_NOTIFIER_HEAD(&priv->notifier_head); + +	priv->dev.class = drm_class; +	priv->dev.type = &drm_privacy_screen_type; +	priv->dev.parent = parent; +	priv->dev.release = drm_privacy_screen_device_release; +	dev_set_name(&priv->dev, "privacy_screen-%s", dev_name(parent)); +	priv->ops = ops; + +	priv->ops->get_hw_state(priv); + +	ret = device_register(&priv->dev); +	if (ret) { +		put_device(&priv->dev); +		return ERR_PTR(ret); +	} + +	mutex_lock(&drm_privacy_screen_devs_lock); +	list_add(&priv->list, &drm_privacy_screen_devs); +	mutex_unlock(&drm_privacy_screen_devs_lock); + +	return priv; +} +EXPORT_SYMBOL(drm_privacy_screen_register); + +/** + * drm_privacy_screen_unregister - unregister privacy-screen + * @priv: privacy-screen to unregister + * + * Unregister a privacy-screen registered with drm_privacy_screen_register(). + * May be called with a NULL or ERR_PTR, in which case it is a no-op. + */ +void drm_privacy_screen_unregister(struct drm_privacy_screen *priv) +{ +	if (IS_ERR_OR_NULL(priv)) +		return; + +	mutex_lock(&drm_privacy_screen_devs_lock); +	list_del(&priv->list); +	mutex_unlock(&drm_privacy_screen_devs_lock); + +	mutex_lock(&priv->lock); +	priv->ops = NULL; +	mutex_unlock(&priv->lock); + +	device_unregister(&priv->dev); +} +EXPORT_SYMBOL(drm_privacy_screen_unregister); + +/** + * drm_privacy_screen_call_notifier_chain - notify consumers of state change + * @priv: Privacy screen to register the notifier with + * + * A privacy-screen provider driver can call this functions upon external + * changes to the privacy-screen state. E.g. the state may be changed by the + * hardware itself in response to a hotkey press. + * This function must be called without holding the privacy-screen lock. + * the driver must update sw_state and hw_state to reflect the new state before + * calling this function. + * The expected behavior from the driver upon receiving an external state + * change event is: 1. Take the lock; 2. Update sw_state and hw_state; + * 3. Release the lock. 4. Call drm_privacy_screen_call_notifier_chain(). + */ +void drm_privacy_screen_call_notifier_chain(struct drm_privacy_screen *priv) +{ +	blocking_notifier_call_chain(&priv->notifier_head, 0, priv); +} +EXPORT_SYMBOL(drm_privacy_screen_call_notifier_chain); |