xref: /openbmc/linux/drivers/gpu/drm/drm_sysfs.c (revision 0d49f303)
1c0e09200SDave Airlie 
2c0e09200SDave Airlie /*
3c0e09200SDave Airlie  * drm_sysfs.c - Modifications to drm_sysfs_class.c to support
4c0e09200SDave Airlie  *               extra sysfs attribute from DRM. Normal drm_sysfs_class
5c0e09200SDave Airlie  *               does not allow adding attributes.
6c0e09200SDave Airlie  *
7c0e09200SDave Airlie  * Copyright (c) 2004 Jon Smirl <jonsmirl@gmail.com>
8c0e09200SDave Airlie  * Copyright (c) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
9c0e09200SDave Airlie  * Copyright (c) 2003-2004 IBM Corp.
10c0e09200SDave Airlie  *
11c0e09200SDave Airlie  * This file is released under the GPLv2
12c0e09200SDave Airlie  *
13c0e09200SDave Airlie  */
14c0e09200SDave Airlie 
15c0e09200SDave Airlie #include <linux/device.h>
16c0e09200SDave Airlie #include <linux/kdev_t.h>
175a0e3ad6STejun Heo #include <linux/gfp.h>
18c0e09200SDave Airlie #include <linux/err.h>
192d1a8a48SPaul Gortmaker #include <linux/export.h>
20c0e09200SDave Airlie 
21760285e7SDavid Howells #include <drm/drm_sysfs.h>
22760285e7SDavid Howells #include <drm/drmP.h>
2367d0ec4eSDaniel Vetter #include "drm_internal.h"
24c0e09200SDave Airlie 
255bdebb18SDave Airlie #define to_drm_minor(d) dev_get_drvdata(d)
265bdebb18SDave Airlie #define to_drm_connector(d) dev_get_drvdata(d)
27c0e09200SDave Airlie 
28e2271704SDaniel Vetter /**
29e2271704SDaniel Vetter  * DOC: overview
30e2271704SDaniel Vetter  *
31e2271704SDaniel Vetter  * DRM provides very little additional support to drivers for sysfs
32e2271704SDaniel Vetter  * interactions, beyond just all the standard stuff. Drivers who want to expose
33e2271704SDaniel Vetter  * additional sysfs properties and property groups can attach them at either
34e2271704SDaniel Vetter  * &drm_device.dev or &drm_connector.kdev.
35e2271704SDaniel Vetter  *
36e2271704SDaniel Vetter  * Registration is automatically handled when calling drm_dev_register(), or
37e2271704SDaniel Vetter  * drm_connector_register() in case of hot-plugged connectors. Unregistration is
38e2271704SDaniel Vetter  * also automatically handled by drm_dev_unregister() and
39e2271704SDaniel Vetter  * drm_connector_unregister().
40e2271704SDaniel Vetter  */
41e2271704SDaniel Vetter 
4208e4d534SThomas Hellstrom static struct device_type drm_sysfs_device_minor = {
4308e4d534SThomas Hellstrom 	.name = "drm_minor"
4408e4d534SThomas Hellstrom };
4508e4d534SThomas Hellstrom 
46fcc90213SDavid Herrmann struct class *drm_class;
47fcc90213SDavid Herrmann 
482c9ede55SAl Viro static char *drm_devnode(struct device *dev, umode_t *mode)
4902200d06SKay Sievers {
5002200d06SKay Sievers 	return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
5102200d06SKay Sievers }
5202200d06SKay Sievers 
5382d5e73fSDavid Herrmann static CLASS_ATTR_STRING(version, S_IRUGO, "drm 1.1.0 20060810");
54c0e09200SDave Airlie 
55c0e09200SDave Airlie /**
56fcc90213SDavid Herrmann  * drm_sysfs_init - initialize sysfs helpers
57c0e09200SDave Airlie  *
58fcc90213SDavid Herrmann  * This is used to create the DRM class, which is the implicit parent of any
59fcc90213SDavid Herrmann  * other top-level DRM sysfs objects.
60c0e09200SDave Airlie  *
61fcc90213SDavid Herrmann  * You must call drm_sysfs_destroy() to release the allocated resources.
62fcc90213SDavid Herrmann  *
63fcc90213SDavid Herrmann  * Return: 0 on success, negative error code on failure.
64c0e09200SDave Airlie  */
65fcc90213SDavid Herrmann int drm_sysfs_init(void)
66c0e09200SDave Airlie {
67c0e09200SDave Airlie 	int err;
68c0e09200SDave Airlie 
69fcc90213SDavid Herrmann 	drm_class = class_create(THIS_MODULE, "drm");
70fcc90213SDavid Herrmann 	if (IS_ERR(drm_class))
71fcc90213SDavid Herrmann 		return PTR_ERR(drm_class);
72fcc90213SDavid Herrmann 
73fcc90213SDavid Herrmann 	err = class_create_file(drm_class, &class_attr_version.attr);
74fcc90213SDavid Herrmann 	if (err) {
75fcc90213SDavid Herrmann 		class_destroy(drm_class);
76fcc90213SDavid Herrmann 		drm_class = NULL;
77fcc90213SDavid Herrmann 		return err;
78c0e09200SDave Airlie 	}
79c0e09200SDave Airlie 
80fcc90213SDavid Herrmann 	drm_class->devnode = drm_devnode;
81fcc90213SDavid Herrmann 	return 0;
82c0e09200SDave Airlie }
83c0e09200SDave Airlie 
84c0e09200SDave Airlie /**
85c0e09200SDave Airlie  * drm_sysfs_destroy - destroys DRM class
86c0e09200SDave Airlie  *
87c0e09200SDave Airlie  * Destroy the DRM device class.
88c0e09200SDave Airlie  */
89c0e09200SDave Airlie void drm_sysfs_destroy(void)
90c0e09200SDave Airlie {
9126b91ae4SDavid Herrmann 	if (IS_ERR_OR_NULL(drm_class))
92c0e09200SDave Airlie 		return;
930933e2d9SAndi Kleen 	class_remove_file(drm_class, &class_attr_version.attr);
94c0e09200SDave Airlie 	class_destroy(drm_class);
9549099c49SDave Airlie 	drm_class = NULL;
96c0e09200SDave Airlie }
97c0e09200SDave Airlie 
98f453ba04SDave Airlie /*
99f453ba04SDave Airlie  * Connector properties
100f453ba04SDave Airlie  */
101c484f02dSChris Wilson static ssize_t status_store(struct device *device,
102c484f02dSChris Wilson 			   struct device_attribute *attr,
103c484f02dSChris Wilson 			   const char *buf, size_t count)
104c484f02dSChris Wilson {
105c484f02dSChris Wilson 	struct drm_connector *connector = to_drm_connector(device);
106c484f02dSChris Wilson 	struct drm_device *dev = connector->dev;
107ed293f77SDaniel Vetter 	enum drm_connector_force old_force;
108c484f02dSChris Wilson 	int ret;
109c484f02dSChris Wilson 
110c484f02dSChris Wilson 	ret = mutex_lock_interruptible(&dev->mode_config.mutex);
111c484f02dSChris Wilson 	if (ret)
112c484f02dSChris Wilson 		return ret;
113c484f02dSChris Wilson 
114ed293f77SDaniel Vetter 	old_force = connector->force;
115c484f02dSChris Wilson 
116ed293f77SDaniel Vetter 	if (sysfs_streq(buf, "detect"))
117c484f02dSChris Wilson 		connector->force = 0;
118ed293f77SDaniel Vetter 	else if (sysfs_streq(buf, "on"))
119c484f02dSChris Wilson 		connector->force = DRM_FORCE_ON;
120ed293f77SDaniel Vetter 	else if (sysfs_streq(buf, "on-digital"))
121c484f02dSChris Wilson 		connector->force = DRM_FORCE_ON_DIGITAL;
122ed293f77SDaniel Vetter 	else if (sysfs_streq(buf, "off"))
123c484f02dSChris Wilson 		connector->force = DRM_FORCE_OFF;
124ed293f77SDaniel Vetter 	else
125c484f02dSChris Wilson 		ret = -EINVAL;
126c484f02dSChris Wilson 
127ed293f77SDaniel Vetter 	if (old_force != connector->force || !connector->force) {
128ed293f77SDaniel Vetter 		DRM_DEBUG_KMS("[CONNECTOR:%d:%s] force updated from %d to %d or reprobing\n",
129c484f02dSChris Wilson 			      connector->base.id,
130c484f02dSChris Wilson 			      connector->name,
131ed293f77SDaniel Vetter 			      old_force, connector->force);
132c484f02dSChris Wilson 
133ed293f77SDaniel Vetter 		connector->funcs->fill_modes(connector,
134ed293f77SDaniel Vetter 					     dev->mode_config.max_width,
135ed293f77SDaniel Vetter 					     dev->mode_config.max_height);
136c484f02dSChris Wilson 	}
137c484f02dSChris Wilson 
138c484f02dSChris Wilson 	mutex_unlock(&dev->mode_config.mutex);
139c484f02dSChris Wilson 
14038d8571dSRussell King 	return ret ? ret : count;
141c484f02dSChris Wilson }
142c484f02dSChris Wilson 
143f453ba04SDave Airlie static ssize_t status_show(struct device *device,
144f453ba04SDave Airlie 			   struct device_attribute *attr,
145f453ba04SDave Airlie 			   char *buf)
146f453ba04SDave Airlie {
147f453ba04SDave Airlie 	struct drm_connector *connector = to_drm_connector(device);
1484eb9b945SDaniel Vetter 	enum drm_connector_status status;
1494eb9b945SDaniel Vetter 
1504eb9b945SDaniel Vetter 	status = READ_ONCE(connector->status);
151007c80a5SChris Wilson 
15275185c92SKeith Packard 	return snprintf(buf, PAGE_SIZE, "%s\n",
1534eb9b945SDaniel Vetter 			drm_get_connector_status_name(status));
154f453ba04SDave Airlie }
155f453ba04SDave Airlie 
156f453ba04SDave Airlie static ssize_t dpms_show(struct device *device,
157f453ba04SDave Airlie 			   struct device_attribute *attr,
158f453ba04SDave Airlie 			   char *buf)
159f453ba04SDave Airlie {
160f453ba04SDave Airlie 	struct drm_connector *connector = to_drm_connector(device);
161621bd0f6SDaniel Vetter 	int dpms;
162f453ba04SDave Airlie 
163621bd0f6SDaniel Vetter 	dpms = READ_ONCE(connector->dpms);
164f453ba04SDave Airlie 
16575185c92SKeith Packard 	return snprintf(buf, PAGE_SIZE, "%s\n",
166621bd0f6SDaniel Vetter 			drm_get_dpms_name(dpms));
167f453ba04SDave Airlie }
168f453ba04SDave Airlie 
169f453ba04SDave Airlie static ssize_t enabled_show(struct device *device,
170f453ba04SDave Airlie 			    struct device_attribute *attr,
171f453ba04SDave Airlie 			   char *buf)
172f453ba04SDave Airlie {
173f453ba04SDave Airlie 	struct drm_connector *connector = to_drm_connector(device);
1744eb9b945SDaniel Vetter 	bool enabled;
175f453ba04SDave Airlie 
1764eb9b945SDaniel Vetter 	enabled = READ_ONCE(connector->encoder);
1774eb9b945SDaniel Vetter 
1784eb9b945SDaniel Vetter 	return snprintf(buf, PAGE_SIZE, enabled ? "enabled\n" : "disabled\n");
179f453ba04SDave Airlie }
180f453ba04SDave Airlie 
1812c3c8beaSChris Wright static ssize_t edid_show(struct file *filp, struct kobject *kobj,
1822c3c8beaSChris Wright 			 struct bin_attribute *attr, char *buf, loff_t off,
1832c3c8beaSChris Wright 			 size_t count)
184f453ba04SDave Airlie {
185d122cbf1SGeliang Tang 	struct device *connector_dev = kobj_to_dev(kobj);
186f453ba04SDave Airlie 	struct drm_connector *connector = to_drm_connector(connector_dev);
187f453ba04SDave Airlie 	unsigned char *edid;
188f453ba04SDave Airlie 	size_t size;
189a48a62bcSDaniel Vetter 	ssize_t ret = 0;
190f453ba04SDave Airlie 
191a48a62bcSDaniel Vetter 	mutex_lock(&connector->dev->mode_config.mutex);
192f453ba04SDave Airlie 	if (!connector->edid_blob_ptr)
193a48a62bcSDaniel Vetter 		goto unlock;
194f453ba04SDave Airlie 
195f453ba04SDave Airlie 	edid = connector->edid_blob_ptr->data;
196f453ba04SDave Airlie 	size = connector->edid_blob_ptr->length;
197f453ba04SDave Airlie 	if (!edid)
198a48a62bcSDaniel Vetter 		goto unlock;
199f453ba04SDave Airlie 
200f453ba04SDave Airlie 	if (off >= size)
201a48a62bcSDaniel Vetter 		goto unlock;
202f453ba04SDave Airlie 
203f453ba04SDave Airlie 	if (off + count > size)
204f453ba04SDave Airlie 		count = size - off;
205f453ba04SDave Airlie 	memcpy(buf, edid + off, count);
206f453ba04SDave Airlie 
207a48a62bcSDaniel Vetter 	ret = count;
208a48a62bcSDaniel Vetter unlock:
209a48a62bcSDaniel Vetter 	mutex_unlock(&connector->dev->mode_config.mutex);
210a48a62bcSDaniel Vetter 
211a48a62bcSDaniel Vetter 	return ret;
212f453ba04SDave Airlie }
213f453ba04SDave Airlie 
214f453ba04SDave Airlie static ssize_t modes_show(struct device *device,
215f453ba04SDave Airlie 			   struct device_attribute *attr,
216f453ba04SDave Airlie 			   char *buf)
217f453ba04SDave Airlie {
218f453ba04SDave Airlie 	struct drm_connector *connector = to_drm_connector(device);
219f453ba04SDave Airlie 	struct drm_display_mode *mode;
220f453ba04SDave Airlie 	int written = 0;
221f453ba04SDave Airlie 
222a48a62bcSDaniel Vetter 	mutex_lock(&connector->dev->mode_config.mutex);
223f453ba04SDave Airlie 	list_for_each_entry(mode, &connector->modes, head) {
224f453ba04SDave Airlie 		written += snprintf(buf + written, PAGE_SIZE - written, "%s\n",
225f453ba04SDave Airlie 				    mode->name);
226f453ba04SDave Airlie 	}
227a48a62bcSDaniel Vetter 	mutex_unlock(&connector->dev->mode_config.mutex);
228f453ba04SDave Airlie 
229f453ba04SDave Airlie 	return written;
230f453ba04SDave Airlie }
231f453ba04SDave Airlie 
232c484f02dSChris Wilson static DEVICE_ATTR_RW(status);
233335f1a62STakashi Iwai static DEVICE_ATTR_RO(enabled);
234335f1a62STakashi Iwai static DEVICE_ATTR_RO(dpms);
235335f1a62STakashi Iwai static DEVICE_ATTR_RO(modes);
236335f1a62STakashi Iwai 
237335f1a62STakashi Iwai static struct attribute *connector_dev_attrs[] = {
238335f1a62STakashi Iwai 	&dev_attr_status.attr,
239335f1a62STakashi Iwai 	&dev_attr_enabled.attr,
240335f1a62STakashi Iwai 	&dev_attr_dpms.attr,
241335f1a62STakashi Iwai 	&dev_attr_modes.attr,
242335f1a62STakashi Iwai 	NULL
243f453ba04SDave Airlie };
244f453ba04SDave Airlie 
245f453ba04SDave Airlie static struct bin_attribute edid_attr = {
246f453ba04SDave Airlie 	.attr.name = "edid",
247e36ebaf4SKeith Packard 	.attr.mode = 0444,
2487466f4ccSAdam Jackson 	.size = 0,
249f453ba04SDave Airlie 	.read = edid_show,
250f453ba04SDave Airlie };
251f453ba04SDave Airlie 
252335f1a62STakashi Iwai static struct bin_attribute *connector_bin_attrs[] = {
253335f1a62STakashi Iwai 	&edid_attr,
254335f1a62STakashi Iwai 	NULL
255335f1a62STakashi Iwai };
256335f1a62STakashi Iwai 
257335f1a62STakashi Iwai static const struct attribute_group connector_dev_group = {
258335f1a62STakashi Iwai 	.attrs = connector_dev_attrs,
259335f1a62STakashi Iwai 	.bin_attrs = connector_bin_attrs,
260335f1a62STakashi Iwai };
261335f1a62STakashi Iwai 
262335f1a62STakashi Iwai static const struct attribute_group *connector_dev_groups[] = {
263335f1a62STakashi Iwai 	&connector_dev_group,
264335f1a62STakashi Iwai 	NULL
265335f1a62STakashi Iwai };
266335f1a62STakashi Iwai 
267f453ba04SDave Airlie int drm_sysfs_connector_add(struct drm_connector *connector)
268f453ba04SDave Airlie {
269f453ba04SDave Airlie 	struct drm_device *dev = connector->dev;
270f453ba04SDave Airlie 
2715bdebb18SDave Airlie 	if (connector->kdev)
2725bdebb18SDave Airlie 		return 0;
2735bdebb18SDave Airlie 
274335f1a62STakashi Iwai 	connector->kdev =
275335f1a62STakashi Iwai 		device_create_with_groups(drm_class, dev->primary->kdev, 0,
276335f1a62STakashi Iwai 					  connector, connector_dev_groups,
277335f1a62STakashi Iwai 					  "card%d-%s", dev->primary->index,
278335f1a62STakashi Iwai 					  connector->name);
279f453ba04SDave Airlie 	DRM_DEBUG("adding \"%s\" to sysfs\n",
28025933820SJani Nikula 		  connector->name);
281f453ba04SDave Airlie 
2825bdebb18SDave Airlie 	if (IS_ERR(connector->kdev)) {
2835bdebb18SDave Airlie 		DRM_ERROR("failed to register connector device: %ld\n", PTR_ERR(connector->kdev));
284335f1a62STakashi Iwai 		return PTR_ERR(connector->kdev);
285f453ba04SDave Airlie 	}
286f453ba04SDave Airlie 
287f453ba04SDave Airlie 	/* Let userspace know we have a new connector */
288f453ba04SDave Airlie 	drm_sysfs_hotplug_event(dev);
289f453ba04SDave Airlie 
290f453ba04SDave Airlie 	return 0;
291f453ba04SDave Airlie }
292f453ba04SDave Airlie 
293f453ba04SDave Airlie void drm_sysfs_connector_remove(struct drm_connector *connector)
294f453ba04SDave Airlie {
2955bdebb18SDave Airlie 	if (!connector->kdev)
2961828fe6cSDave Airlie 		return;
297f453ba04SDave Airlie 	DRM_DEBUG("removing \"%s\" from sysfs\n",
29825933820SJani Nikula 		  connector->name);
299f453ba04SDave Airlie 
3005bdebb18SDave Airlie 	device_unregister(connector->kdev);
3015bdebb18SDave Airlie 	connector->kdev = NULL;
302f453ba04SDave Airlie }
303f453ba04SDave Airlie 
304f453ba04SDave Airlie /**
305f453ba04SDave Airlie  * drm_sysfs_hotplug_event - generate a DRM uevent
306f453ba04SDave Airlie  * @dev: DRM device
307f453ba04SDave Airlie  *
308f453ba04SDave Airlie  * Send a uevent for the DRM device specified by @dev.  Currently we only
309f453ba04SDave Airlie  * set HOTPLUG=1 in the uevent environment, but this could be expanded to
310f453ba04SDave Airlie  * deal with other types of events.
311f453ba04SDave Airlie  */
312f453ba04SDave Airlie void drm_sysfs_hotplug_event(struct drm_device *dev)
313f453ba04SDave Airlie {
314f453ba04SDave Airlie 	char *event_string = "HOTPLUG=1";
315f453ba04SDave Airlie 	char *envp[] = { event_string, NULL };
316f453ba04SDave Airlie 
317f453ba04SDave Airlie 	DRM_DEBUG("generating hotplug event\n");
318f453ba04SDave Airlie 
3195bdebb18SDave Airlie 	kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
320f453ba04SDave Airlie }
3215ca58282SJesse Barnes EXPORT_SYMBOL(drm_sysfs_hotplug_event);
322f453ba04SDave Airlie 
323760c960bSDavid Herrmann static void drm_sysfs_release(struct device *dev)
324760c960bSDavid Herrmann {
325760c960bSDavid Herrmann 	kfree(dev);
326760c960bSDavid Herrmann }
327760c960bSDavid Herrmann 
328e1728075SDavid Herrmann struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
329c0e09200SDave Airlie {
330e1728075SDavid Herrmann 	const char *minor_str;
331e1728075SDavid Herrmann 	struct device *kdev;
332760c960bSDavid Herrmann 	int r;
333c0e09200SDave Airlie 
3340d49f303SDaniel Vetter 	if (minor->type == DRM_MINOR_RENDER)
335f453ba04SDave Airlie 		minor_str = "renderD%d";
336f453ba04SDave Airlie 	else
337c0e09200SDave Airlie 		minor_str = "card%d";
338c0e09200SDave Airlie 
339e1728075SDavid Herrmann 	kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
340e1728075SDavid Herrmann 	if (!kdev)
341e1728075SDavid Herrmann 		return ERR_PTR(-ENOMEM);
342760c960bSDavid Herrmann 
343e1728075SDavid Herrmann 	device_initialize(kdev);
344e1728075SDavid Herrmann 	kdev->devt = MKDEV(DRM_MAJOR, minor->index);
345e1728075SDavid Herrmann 	kdev->class = drm_class;
346e1728075SDavid Herrmann 	kdev->type = &drm_sysfs_device_minor;
347e1728075SDavid Herrmann 	kdev->parent = minor->dev->dev;
348e1728075SDavid Herrmann 	kdev->release = drm_sysfs_release;
349e1728075SDavid Herrmann 	dev_set_drvdata(kdev, minor);
350760c960bSDavid Herrmann 
351e1728075SDavid Herrmann 	r = dev_set_name(kdev, minor_str, minor->index);
352760c960bSDavid Herrmann 	if (r < 0)
353e1728075SDavid Herrmann 		goto err_free;
354760c960bSDavid Herrmann 
355e1728075SDavid Herrmann 	return kdev;
356760c960bSDavid Herrmann 
357e1728075SDavid Herrmann err_free:
358e1728075SDavid Herrmann 	put_device(kdev);
359e1728075SDavid Herrmann 	return ERR_PTR(r);
360c0e09200SDave Airlie }
361c0e09200SDave Airlie 
362c0e09200SDave Airlie /**
363e2271704SDaniel Vetter  * drm_class_device_register - register new device with the DRM sysfs class
364e2271704SDaniel Vetter  * @dev: device to register
365327c225bSThomas Hellstrom  *
366e2271704SDaniel Vetter  * Registers a new &struct device within the DRM sysfs class. Essentially only
367e2271704SDaniel Vetter  * used by ttm to have a place for its global settings. Drivers should never use
368e2271704SDaniel Vetter  * this.
369327c225bSThomas Hellstrom  */
370327c225bSThomas Hellstrom int drm_class_device_register(struct device *dev)
371327c225bSThomas Hellstrom {
37249099c49SDave Airlie 	if (!drm_class || IS_ERR(drm_class))
37349099c49SDave Airlie 		return -ENOENT;
37449099c49SDave Airlie 
375327c225bSThomas Hellstrom 	dev->class = drm_class;
376327c225bSThomas Hellstrom 	return device_register(dev);
377327c225bSThomas Hellstrom }
378327c225bSThomas Hellstrom EXPORT_SYMBOL_GPL(drm_class_device_register);
379327c225bSThomas Hellstrom 
380e2271704SDaniel Vetter /**
381e2271704SDaniel Vetter  * drm_class_device_unregister - unregister device with the DRM sysfs class
382e2271704SDaniel Vetter  * @dev: device to unregister
383e2271704SDaniel Vetter  *
384e2271704SDaniel Vetter  * Unregisters a &struct device from the DRM sysfs class. Essentially only used
385e2271704SDaniel Vetter  * by ttm to have a place for its global settings. Drivers should never use
386e2271704SDaniel Vetter  * this.
387e2271704SDaniel Vetter  */
388327c225bSThomas Hellstrom void drm_class_device_unregister(struct device *dev)
389327c225bSThomas Hellstrom {
390327c225bSThomas Hellstrom 	return device_unregister(dev);
391327c225bSThomas Hellstrom }
392327c225bSThomas Hellstrom EXPORT_SYMBOL_GPL(drm_class_device_unregister);
393