123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- // 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
- * @data: Private data owned by the privacy screen provider
- *
- * 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,
- void *data)
- {
- 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->drvdata = data;
- 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->drvdata = NULL;
- 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);
|