mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
467 lines
14 KiB
467 lines
14 KiB
// 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);
|
|
|