318f6e599d
commit ce85882860f0e756f7066cbda1c43e8b50b73ab6 upstream. Sending the exact same hotplug event is not great uapi. Luckily the only already merged implementation of leases (in the -modesetting driver) doesn't care about what kind of uevent it gets, and unconditionally processes both hotplug and lease changes. So we can still adjust the uapi here. But e.g. weston tries to filter stuff, and I guess others might want to do that too. Try to make that possible. Cc: stable since it's uapi adjustement that we want to roll out everywhere. Michel Dänzer mentioned on irc that -amdgpu also has lease support. It has the same code flow as -modesetting though, so we can still go ahead. v2: Mention -amdgpu (Michel) Cc: Keith Packard <keithp@keithp.com> Cc: Dave Airlie <airlied@redhat.com> Cc: stable@vger.kernel.org Reviewed-by: Keith Packard <keithp@keithp.com> Signed-off-by: Daniel Vetter <daniel.vetter@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20181129094226.30591-1-daniel.vetter@ffwll.ch Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
402 lines
9.5 KiB
C
402 lines
9.5 KiB
C
|
|
/*
|
|
* drm_sysfs.c - Modifications to drm_sysfs_class.c to support
|
|
* extra sysfs attribute from DRM. Normal drm_sysfs_class
|
|
* does not allow adding attributes.
|
|
*
|
|
* Copyright (c) 2004 Jon Smirl <jonsmirl@gmail.com>
|
|
* Copyright (c) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
|
|
* Copyright (c) 2003-2004 IBM Corp.
|
|
*
|
|
* This file is released under the GPLv2
|
|
*
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/kdev_t.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/err.h>
|
|
#include <linux/export.h>
|
|
|
|
#include <drm/drm_sysfs.h>
|
|
#include <drm/drmP.h>
|
|
#include "drm_internal.h"
|
|
|
|
#define to_drm_minor(d) dev_get_drvdata(d)
|
|
#define to_drm_connector(d) dev_get_drvdata(d)
|
|
|
|
/**
|
|
* DOC: overview
|
|
*
|
|
* DRM provides very little additional support to drivers for sysfs
|
|
* interactions, beyond just all the standard stuff. Drivers who want to expose
|
|
* additional sysfs properties and property groups can attach them at either
|
|
* &drm_device.dev or &drm_connector.kdev.
|
|
*
|
|
* Registration is automatically handled when calling drm_dev_register(), or
|
|
* drm_connector_register() in case of hot-plugged connectors. Unregistration is
|
|
* also automatically handled by drm_dev_unregister() and
|
|
* drm_connector_unregister().
|
|
*/
|
|
|
|
static struct device_type drm_sysfs_device_minor = {
|
|
.name = "drm_minor"
|
|
};
|
|
|
|
struct class *drm_class;
|
|
|
|
static char *drm_devnode(struct device *dev, umode_t *mode)
|
|
{
|
|
return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
|
|
}
|
|
|
|
static CLASS_ATTR_STRING(version, S_IRUGO, "drm 1.1.0 20060810");
|
|
|
|
/**
|
|
* drm_sysfs_init - initialize sysfs helpers
|
|
*
|
|
* This is used to create the DRM class, which is the implicit parent of any
|
|
* other top-level DRM sysfs objects.
|
|
*
|
|
* You must call drm_sysfs_destroy() to release the allocated resources.
|
|
*
|
|
* Return: 0 on success, negative error code on failure.
|
|
*/
|
|
int drm_sysfs_init(void)
|
|
{
|
|
int err;
|
|
|
|
drm_class = class_create(THIS_MODULE, "drm");
|
|
if (IS_ERR(drm_class))
|
|
return PTR_ERR(drm_class);
|
|
|
|
err = class_create_file(drm_class, &class_attr_version.attr);
|
|
if (err) {
|
|
class_destroy(drm_class);
|
|
drm_class = NULL;
|
|
return err;
|
|
}
|
|
|
|
drm_class->devnode = drm_devnode;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* drm_sysfs_destroy - destroys DRM class
|
|
*
|
|
* Destroy the DRM device class.
|
|
*/
|
|
void drm_sysfs_destroy(void)
|
|
{
|
|
if (IS_ERR_OR_NULL(drm_class))
|
|
return;
|
|
class_remove_file(drm_class, &class_attr_version.attr);
|
|
class_destroy(drm_class);
|
|
drm_class = NULL;
|
|
}
|
|
|
|
/*
|
|
* Connector properties
|
|
*/
|
|
static ssize_t status_store(struct device *device,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
struct drm_device *dev = connector->dev;
|
|
enum drm_connector_force old_force;
|
|
int ret;
|
|
|
|
ret = mutex_lock_interruptible(&dev->mode_config.mutex);
|
|
if (ret)
|
|
return ret;
|
|
|
|
old_force = connector->force;
|
|
|
|
if (sysfs_streq(buf, "detect"))
|
|
connector->force = 0;
|
|
else if (sysfs_streq(buf, "on"))
|
|
connector->force = DRM_FORCE_ON;
|
|
else if (sysfs_streq(buf, "on-digital"))
|
|
connector->force = DRM_FORCE_ON_DIGITAL;
|
|
else if (sysfs_streq(buf, "off"))
|
|
connector->force = DRM_FORCE_OFF;
|
|
else
|
|
ret = -EINVAL;
|
|
|
|
if (old_force != connector->force || !connector->force) {
|
|
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] force updated from %d to %d or reprobing\n",
|
|
connector->base.id,
|
|
connector->name,
|
|
old_force, connector->force);
|
|
|
|
connector->funcs->fill_modes(connector,
|
|
dev->mode_config.max_width,
|
|
dev->mode_config.max_height);
|
|
}
|
|
|
|
mutex_unlock(&dev->mode_config.mutex);
|
|
|
|
return ret ? ret : count;
|
|
}
|
|
|
|
static ssize_t status_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
enum drm_connector_status status;
|
|
|
|
status = READ_ONCE(connector->status);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
|
drm_get_connector_status_name(status));
|
|
}
|
|
|
|
static ssize_t dpms_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
int dpms;
|
|
|
|
dpms = READ_ONCE(connector->dpms);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
|
drm_get_dpms_name(dpms));
|
|
}
|
|
|
|
static ssize_t enabled_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
bool enabled;
|
|
|
|
enabled = READ_ONCE(connector->encoder);
|
|
|
|
return snprintf(buf, PAGE_SIZE, enabled ? "enabled\n" : "disabled\n");
|
|
}
|
|
|
|
static ssize_t edid_show(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *attr, char *buf, loff_t off,
|
|
size_t count)
|
|
{
|
|
struct device *connector_dev = kobj_to_dev(kobj);
|
|
struct drm_connector *connector = to_drm_connector(connector_dev);
|
|
unsigned char *edid;
|
|
size_t size;
|
|
ssize_t ret = 0;
|
|
|
|
mutex_lock(&connector->dev->mode_config.mutex);
|
|
if (!connector->edid_blob_ptr)
|
|
goto unlock;
|
|
|
|
edid = connector->edid_blob_ptr->data;
|
|
size = connector->edid_blob_ptr->length;
|
|
if (!edid)
|
|
goto unlock;
|
|
|
|
if (off >= size)
|
|
goto unlock;
|
|
|
|
if (off + count > size)
|
|
count = size - off;
|
|
memcpy(buf, edid + off, count);
|
|
|
|
ret = count;
|
|
unlock:
|
|
mutex_unlock(&connector->dev->mode_config.mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t modes_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
struct drm_display_mode *mode;
|
|
int written = 0;
|
|
|
|
mutex_lock(&connector->dev->mode_config.mutex);
|
|
list_for_each_entry(mode, &connector->modes, head) {
|
|
written += snprintf(buf + written, PAGE_SIZE - written, "%s\n",
|
|
mode->name);
|
|
}
|
|
mutex_unlock(&connector->dev->mode_config.mutex);
|
|
|
|
return written;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(status);
|
|
static DEVICE_ATTR_RO(enabled);
|
|
static DEVICE_ATTR_RO(dpms);
|
|
static DEVICE_ATTR_RO(modes);
|
|
|
|
static struct attribute *connector_dev_attrs[] = {
|
|
&dev_attr_status.attr,
|
|
&dev_attr_enabled.attr,
|
|
&dev_attr_dpms.attr,
|
|
&dev_attr_modes.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct bin_attribute edid_attr = {
|
|
.attr.name = "edid",
|
|
.attr.mode = 0444,
|
|
.size = 0,
|
|
.read = edid_show,
|
|
};
|
|
|
|
static struct bin_attribute *connector_bin_attrs[] = {
|
|
&edid_attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group connector_dev_group = {
|
|
.attrs = connector_dev_attrs,
|
|
.bin_attrs = connector_bin_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *connector_dev_groups[] = {
|
|
&connector_dev_group,
|
|
NULL
|
|
};
|
|
|
|
int drm_sysfs_connector_add(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
|
|
if (connector->kdev)
|
|
return 0;
|
|
|
|
connector->kdev =
|
|
device_create_with_groups(drm_class, dev->primary->kdev, 0,
|
|
connector, connector_dev_groups,
|
|
"card%d-%s", dev->primary->index,
|
|
connector->name);
|
|
DRM_DEBUG("adding \"%s\" to sysfs\n",
|
|
connector->name);
|
|
|
|
if (IS_ERR(connector->kdev)) {
|
|
DRM_ERROR("failed to register connector device: %ld\n", PTR_ERR(connector->kdev));
|
|
return PTR_ERR(connector->kdev);
|
|
}
|
|
|
|
/* Let userspace know we have a new connector */
|
|
drm_sysfs_hotplug_event(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void drm_sysfs_connector_remove(struct drm_connector *connector)
|
|
{
|
|
if (!connector->kdev)
|
|
return;
|
|
DRM_DEBUG("removing \"%s\" from sysfs\n",
|
|
connector->name);
|
|
|
|
device_unregister(connector->kdev);
|
|
connector->kdev = NULL;
|
|
}
|
|
|
|
void drm_sysfs_lease_event(struct drm_device *dev)
|
|
{
|
|
char *event_string = "LEASE=1";
|
|
char *envp[] = { event_string, NULL };
|
|
|
|
DRM_DEBUG("generating lease event\n");
|
|
|
|
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
|
|
}
|
|
|
|
/**
|
|
* drm_sysfs_hotplug_event - generate a DRM uevent
|
|
* @dev: DRM device
|
|
*
|
|
* Send a uevent for the DRM device specified by @dev. Currently we only
|
|
* set HOTPLUG=1 in the uevent environment, but this could be expanded to
|
|
* deal with other types of events.
|
|
*/
|
|
void drm_sysfs_hotplug_event(struct drm_device *dev)
|
|
{
|
|
char *event_string = "HOTPLUG=1";
|
|
char *envp[] = { event_string, NULL };
|
|
|
|
DRM_DEBUG("generating hotplug event\n");
|
|
|
|
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
|
|
}
|
|
EXPORT_SYMBOL(drm_sysfs_hotplug_event);
|
|
|
|
static void drm_sysfs_release(struct device *dev)
|
|
{
|
|
kfree(dev);
|
|
}
|
|
|
|
struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
|
|
{
|
|
const char *minor_str;
|
|
struct device *kdev;
|
|
int r;
|
|
|
|
if (minor->type == DRM_MINOR_RENDER)
|
|
minor_str = "renderD%d";
|
|
else
|
|
minor_str = "card%d";
|
|
|
|
kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
|
|
if (!kdev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
device_initialize(kdev);
|
|
kdev->devt = MKDEV(DRM_MAJOR, minor->index);
|
|
kdev->class = drm_class;
|
|
kdev->type = &drm_sysfs_device_minor;
|
|
kdev->parent = minor->dev->dev;
|
|
kdev->release = drm_sysfs_release;
|
|
dev_set_drvdata(kdev, minor);
|
|
|
|
r = dev_set_name(kdev, minor_str, minor->index);
|
|
if (r < 0)
|
|
goto err_free;
|
|
|
|
return kdev;
|
|
|
|
err_free:
|
|
put_device(kdev);
|
|
return ERR_PTR(r);
|
|
}
|
|
|
|
/**
|
|
* drm_class_device_register - register new device with the DRM sysfs class
|
|
* @dev: device to register
|
|
*
|
|
* Registers a new &struct device within the DRM sysfs class. Essentially only
|
|
* used by ttm to have a place for its global settings. Drivers should never use
|
|
* this.
|
|
*/
|
|
int drm_class_device_register(struct device *dev)
|
|
{
|
|
if (!drm_class || IS_ERR(drm_class))
|
|
return -ENOENT;
|
|
|
|
dev->class = drm_class;
|
|
return device_register(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_class_device_register);
|
|
|
|
/**
|
|
* drm_class_device_unregister - unregister device with the DRM sysfs class
|
|
* @dev: device to unregister
|
|
*
|
|
* Unregisters a &struct device from the DRM sysfs class. Essentially only used
|
|
* by ttm to have a place for its global settings. Drivers should never use
|
|
* this.
|
|
*/
|
|
void drm_class_device_unregister(struct device *dev)
|
|
{
|
|
return device_unregister(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_class_device_unregister);
|