Improved DRM sysfs support

This patch ties outputs, output properties and hotplug events into the
DRM core.  Each output has a corresponding directory under the primary
DRM device (usually card0) containing dpms, edid, modes, and connection
status files.

New hotplug change events occur when outputs are added or hotplug events
are detected.
main
Jesse Barnes 2008-04-08 12:42:23 -07:00
parent 09e637848a
commit 5a3ce06f3a
6 changed files with 249 additions and 10 deletions

View File

@ -1311,7 +1311,11 @@ struct drm_sysfs_class;
extern struct class *drm_sysfs_create(struct module *owner, char *name); extern struct class *drm_sysfs_create(struct module *owner, char *name);
extern void drm_sysfs_destroy(void); extern void drm_sysfs_destroy(void);
extern int drm_sysfs_device_add(struct drm_minor *minor); extern int drm_sysfs_device_add(struct drm_minor *minor);
extern void drm_sysfs_hotplug_event(struct drm_device *dev);
extern void drm_sysfs_device_remove(struct drm_minor *minor); extern void drm_sysfs_device_remove(struct drm_minor *minor);
extern char *drm_get_output_status_name(enum drm_output_status status);
extern int drm_sysfs_output_add(struct drm_output *output);
extern void drm_sysfs_output_remove(struct drm_output *output);
/* /*
* Basic memory manager support (drm_mm.c) * Basic memory manager support (drm_mm.c)

View File

@ -47,6 +47,18 @@ static struct drm_prop_enum_list drm_dpms_enum_list[] =
{ DPMSModeSuspend, "Suspend" }, { DPMSModeSuspend, "Suspend" },
{ DPMSModeOff, "Off" } { DPMSModeOff, "Off" }
}; };
char *drm_get_dpms_name(int val)
{
int i;
for (i = 0; i < ARRAY_SIZE(drm_dpms_enum_list); i++)
if (drm_dpms_enum_list[i].type == val)
return drm_dpms_enum_list[i].name;
return "unknown";
}
static struct drm_prop_enum_list drm_conn_enum_list[] = static struct drm_prop_enum_list drm_conn_enum_list[] =
{ { ConnectorUnknown, "Unknown" }, { { ConnectorUnknown, "Unknown" },
{ ConnectorVGA, "VGA" }, { ConnectorVGA, "VGA" },
@ -79,6 +91,16 @@ char *drm_get_output_name(struct drm_output *output)
return buf; return buf;
} }
char *drm_get_output_status_name(enum drm_output_status status)
{
if (status == output_status_connected)
return "connected";
else if (status == output_status_disconnected)
return "disconnected";
else
return "unknown";
}
/** /**
* drm_idr_get - allocate a new identifier * drm_idr_get - allocate a new identifier
* @dev: DRM device * @dev: DRM device
@ -629,6 +651,8 @@ struct drm_output *drm_output_create(struct drm_device *dev,
/* output_set_monitor(output)? */ /* output_set_monitor(output)? */
/* check for output_ignored(output)? */ /* check for output_ignored(output)? */
drm_sysfs_output_add(output);
mutex_lock(&dev->mode_config.mutex); mutex_lock(&dev->mode_config.mutex);
list_add_tail(&output->head, &dev->mode_config.output_list); list_add_tail(&output->head, &dev->mode_config.output_list);
dev->mode_config.num_output++; dev->mode_config.num_output++;
@ -659,6 +683,8 @@ void drm_output_destroy(struct drm_output *output)
struct drm_device *dev = output->dev; struct drm_device *dev = output->dev;
struct drm_display_mode *mode, *t; struct drm_display_mode *mode, *t;
drm_sysfs_output_remove(output);
if (*output->funcs->cleanup) if (*output->funcs->cleanup)
(*output->funcs->cleanup)(output); (*output->funcs->cleanup)(output);
@ -1226,6 +1252,8 @@ int drm_hotplug_stage_two(struct drm_device *dev, struct drm_output *output,
DRM_ERROR("failed to set mode after hotplug\n"); DRM_ERROR("failed to set mode after hotplug\n");
} }
drm_sysfs_hotplug_event(dev);
drm_disable_unused_functions(dev); drm_disable_unused_functions(dev);
return 0; return 0;
@ -2223,6 +2251,24 @@ int drm_output_property_set_value(struct drm_output *output,
} }
EXPORT_SYMBOL(drm_output_property_set_value); EXPORT_SYMBOL(drm_output_property_set_value);
int drm_output_property_get_value(struct drm_output *output,
struct drm_property *property, uint64_t *val)
{
int i;
for (i = 0; i < DRM_OUTPUT_MAX_PROPERTY; i++) {
if (output->property_ids[i] == property->id) {
*val = output->property_values[i];
break;
}
}
if (i == DRM_OUTPUT_MAX_PROPERTY)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL(drm_output_property_get_value);
int drm_mode_getproperty_ioctl(struct drm_device *dev, int drm_mode_getproperty_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv) void *data, struct drm_file *file_priv)
{ {

View File

@ -451,6 +451,8 @@ struct drm_output_funcs {
*/ */
struct drm_output { struct drm_output {
struct drm_device *dev; struct drm_device *dev;
struct device kdev;
struct device_attribute *attr;
struct list_head head; struct list_head head;
struct drm_crtc *crtc; struct drm_crtc *crtc;
int id; /* idr assigned */ int id; /* idr assigned */
@ -563,6 +565,7 @@ struct drm_output *drm_output_create(struct drm_device *dev,
int type); int type);
extern char *drm_get_output_name(struct drm_output *output); extern char *drm_get_output_name(struct drm_output *output);
extern char *drm_get_dpms_name(int val);
extern void drm_output_destroy(struct drm_output *output); extern void drm_output_destroy(struct drm_output *output);
extern void drm_fb_release(struct file *filp); extern void drm_fb_release(struct file *filp);
@ -606,6 +609,9 @@ extern int drm_mode_output_update_edid_property(struct drm_output *output,
extern int drm_output_property_set_value(struct drm_output *output, extern int drm_output_property_set_value(struct drm_output *output,
struct drm_property *property, struct drm_property *property,
uint64_t value); uint64_t value);
extern int drm_output_property_get_value(struct drm_output *output,
struct drm_property *property,
uint64_t *value);
extern struct drm_display_mode *drm_crtc_mode_create(struct drm_device *dev); extern struct drm_display_mode *drm_crtc_mode_create(struct drm_device *dev);
extern bool drm_initial_config(struct drm_device *dev, bool cangrow); extern bool drm_initial_config(struct drm_device *dev, bool cangrow);
extern void drm_framebuffer_set_object(struct drm_device *dev, extern void drm_framebuffer_set_object(struct drm_device *dev,

View File

@ -133,10 +133,6 @@ static ssize_t show_dri(struct device *device, struct device_attribute *attr,
return snprintf(buf, PAGE_SIZE, "%s\n", drm_dev->driver->pci_driver.name); return snprintf(buf, PAGE_SIZE, "%s\n", drm_dev->driver->pci_driver.name);
} }
static struct device_attribute device_attrs[] = {
__ATTR(dri_library_name, S_IRUGO, show_dri, NULL),
};
/** /**
* drm_sysfs_device_release - do nothing * drm_sysfs_device_release - do nothing
* @dev: Linux device * @dev: Linux device
@ -150,6 +146,189 @@ static void drm_sysfs_device_release(struct device *dev)
return; return;
} }
/*
* Output properties
*/
static ssize_t status_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
struct drm_output *output = container_of(device, struct drm_output, kdev);
return snprintf(buf, PAGE_SIZE, "%s",
drm_get_output_status_name(output->funcs->detect(output)));
}
static ssize_t dpms_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
struct drm_output *output = container_of(device, struct drm_output, kdev);
struct drm_device *dev = output->dev;
uint64_t dpms_status;
int ret;
ret = drm_output_property_get_value(output,
dev->mode_config.dpms_property,
&dpms_status);
if (ret)
return 0;
return snprintf(buf, PAGE_SIZE, "%s", drm_get_dpms_name((int)dpms_status));
}
static ssize_t edid_show(struct kobject *kobj, struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct device *output_dev = container_of(kobj, struct device, kobj);
struct drm_output *output = container_of(output_dev, struct drm_output,
kdev);
unsigned char *edid;
size_t size;
if (!output->edid_blob_ptr)
return 0;
edid = output->edid_blob_ptr->data;
size = output->edid_blob_ptr->length;
if (!edid)
return 0;
if (off >= size)
return 0;
if (off + count > size)
count = size - off;
memcpy(buf, edid + off, count);
return count;
}
static ssize_t modes_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
struct drm_output *output = container_of(device, struct drm_output, kdev);
struct drm_display_mode *mode;
int written = 0;
list_for_each_entry(mode, &output->modes, head) {
written += snprintf(buf + written, PAGE_SIZE - written, "%s\n",
mode->name);
}
return written;
}
static struct device_attribute output_attrs[] = {
__ATTR_RO(status),
__ATTR_RO(dpms),
__ATTR_RO(modes),
};
static struct bin_attribute edid_attr = {
.attr.name = "edid",
.size = 128,
.read = edid_show,
};
/**
* drm_sysfs_output_add - add an output to sysfs
* @output: output to add
*
* Create an output device in sysfs, along with its associated output
* properties (so far, connection status, dpms, mode list & edid) and
* generate a hotplug event so userspace knows there's a new output
* available.
*/
int drm_sysfs_output_add(struct drm_output *output)
{
struct drm_device *dev = output->dev;
int ret = 0, i, j;
if (device_is_registered(&output->kdev))
return 0;
output->kdev.parent = &dev->primary->kdev;
output->kdev.class = drm_class;
output->kdev.release = drm_sysfs_device_release;
DRM_DEBUG("adding \"%s\" to sysfs", drm_get_output_name(output));
snprintf(output->kdev.bus_id, BUS_ID_SIZE, "card%d-%s",
dev->primary->index, drm_get_output_name(output));
ret = device_register(&output->kdev);
if (ret) {
DRM_ERROR("failed to register output device: %d\n", ret);
goto out;
}
for (i = 0; i < ARRAY_SIZE(output_attrs); i++) {
ret = device_create_file(&output->kdev, &output_attrs[i]);
if (ret)
goto err_out_files;
}
ret = sysfs_create_bin_file(&output->kdev.kobj, &edid_attr);
if (ret)
goto err_out_files;
/* Let userspace know we have a new output */
drm_sysfs_hotplug_event(dev);
return 0;
err_out_files:
if (i > 0)
for (j = 0; j < i; j++)
device_remove_file(&output->kdev, &output_attrs[i]);
device_unregister(&output->kdev);
out:
return ret;
}
/**
* drm_sysfs_output_remove - remove an output device from sysfs
* @output: output to remove
*
* Remove @output and its associated attributes from sysfs. Note that
* the device model core will take care of sending the "remove" uevent
* at this time, so we don't need to do it.
*/
void drm_sysfs_output_remove(struct drm_output *output)
{
int i;
DRM_DEBUG("removing \"%s\" from sysfs\n", drm_get_output_name(output));
for (i = 0; i < i; i++)
device_remove_file(&output->kdev, &output_attrs[i]);
sysfs_remove_bin_file(&output->kdev.kobj, &edid_attr);
device_unregister(&output->kdev);
}
/**
* 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);
}
static struct device_attribute dri_attrs[] = {
__ATTR(dri_library_name, S_IRUGO, show_dri, NULL),
};
/** /**
* drm_sysfs_device_add - adds a class device to sysfs for a character driver * drm_sysfs_device_add - adds a class device to sysfs for a character driver
* @dev: DRM device to be added * @dev: DRM device to be added
@ -184,8 +363,8 @@ int drm_sysfs_device_add(struct drm_minor *minor)
goto err_out; goto err_out;
} }
for (i = 0; i < ARRAY_SIZE(device_attrs); i++) { for (i = 0; i < ARRAY_SIZE(dri_attrs); i++) {
err = device_create_file(&minor->kdev, &device_attrs[i]); err = device_create_file(&minor->kdev, &dri_attrs[i]);
if (err) if (err)
goto err_out_files; goto err_out_files;
} }
@ -195,7 +374,7 @@ int drm_sysfs_device_add(struct drm_minor *minor)
err_out_files: err_out_files:
if (i > 0) if (i > 0)
for (j = 0; j < i; j++) for (j = 0; j < i; j++)
device_remove_file(&minor->kdev, &device_attrs[i]); device_remove_file(&minor->kdev, &dri_attrs[i]);
device_unregister(&minor->kdev); device_unregister(&minor->kdev);
err_out: err_out:
@ -213,7 +392,7 @@ void drm_sysfs_device_remove(struct drm_minor *minor)
{ {
int i; int i;
for (i = 0; i < ARRAY_SIZE(device_attrs); i++) for (i = 0; i < ARRAY_SIZE(dri_attrs); i++)
device_remove_file(&minor->kdev, &device_attrs[i]); device_remove_file(&minor->kdev, &dri_attrs[i]);
device_unregister(&minor->kdev); device_unregister(&minor->kdev);
} }

View File

@ -1292,7 +1292,10 @@ static void intel_setup_outputs(struct drm_device *dev)
intel_sdvo_init(dev, SDVOB); intel_sdvo_init(dev, SDVOB);
intel_sdvo_init(dev, SDVOC); intel_sdvo_init(dev, SDVOC);
} }
#if 0
if (IS_I9XX(dev) && !IS_I915G(dev))
intel_tv_init(dev);
#endif
list_for_each_entry(output, &dev->mode_config.output_list, head) { list_for_each_entry(output, &dev->mode_config.output_list, head) {
struct intel_output *intel_output = output->driver_private; struct intel_output *intel_output = output->driver_private;
int crtc_mask = 0, clone_mask = 0; int crtc_mask = 0, clone_mask = 0;

View File

@ -68,6 +68,7 @@ extern bool intel_ddc_probe(struct drm_output *output);
extern void intel_crt_init(struct drm_device *dev); extern void intel_crt_init(struct drm_device *dev);
extern void intel_sdvo_init(struct drm_device *dev, int output_device); extern void intel_sdvo_init(struct drm_device *dev, int output_device);
extern void intel_tv_init(struct drm_device *dev);
extern void intel_lvds_init(struct drm_device *dev); extern void intel_lvds_init(struct drm_device *dev);
extern void intel_crtc_load_lut(struct drm_crtc *crtc); extern void intel_crtc_load_lut(struct drm_crtc *crtc);