LCOV - code coverage report
Current view: top level - drivers/gpu/drm - drm_sysfs.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 27 183 14.8 %
Date: 2023-07-19 18:55:55 Functions: 4 22 18.2 %

          Line data    Source code
       1             : // SPDX-License-Identifier: GPL-2.0-only
       2             : 
       3             : /*
       4             :  * drm_sysfs.c - Modifications to drm_sysfs_class.c to support
       5             :  *               extra sysfs attribute from DRM. Normal drm_sysfs_class
       6             :  *               does not allow adding attributes.
       7             :  *
       8             :  * Copyright (c) 2004 Jon Smirl <jonsmirl@gmail.com>
       9             :  * Copyright (c) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
      10             :  * Copyright (c) 2003-2004 IBM Corp.
      11             :  */
      12             : 
      13             : #include <linux/acpi.h>
      14             : #include <linux/component.h>
      15             : #include <linux/device.h>
      16             : #include <linux/err.h>
      17             : #include <linux/export.h>
      18             : #include <linux/gfp.h>
      19             : #include <linux/i2c.h>
      20             : #include <linux/kdev_t.h>
      21             : #include <linux/property.h>
      22             : #include <linux/slab.h>
      23             : 
      24             : #include <drm/drm_accel.h>
      25             : #include <drm/drm_connector.h>
      26             : #include <drm/drm_device.h>
      27             : #include <drm/drm_file.h>
      28             : #include <drm/drm_modes.h>
      29             : #include <drm/drm_print.h>
      30             : #include <drm/drm_property.h>
      31             : #include <drm/drm_sysfs.h>
      32             : 
      33             : #include "drm_internal.h"
      34             : #include "drm_crtc_internal.h"
      35             : 
      36             : #define to_drm_minor(d) dev_get_drvdata(d)
      37             : #define to_drm_connector(d) dev_get_drvdata(d)
      38             : 
      39             : /**
      40             :  * DOC: overview
      41             :  *
      42             :  * DRM provides very little additional support to drivers for sysfs
      43             :  * interactions, beyond just all the standard stuff. Drivers who want to expose
      44             :  * additional sysfs properties and property groups can attach them at either
      45             :  * &drm_device.dev or &drm_connector.kdev.
      46             :  *
      47             :  * Registration is automatically handled when calling drm_dev_register(), or
      48             :  * drm_connector_register() in case of hot-plugged connectors. Unregistration is
      49             :  * also automatically handled by drm_dev_unregister() and
      50             :  * drm_connector_unregister().
      51             :  */
      52             : 
      53             : static struct device_type drm_sysfs_device_minor = {
      54             :         .name = "drm_minor"
      55             : };
      56             : 
      57             : static struct device_type drm_sysfs_device_connector = {
      58             :         .name = "drm_connector",
      59             : };
      60             : 
      61             : struct class *drm_class;
      62             : 
      63             : #ifdef CONFIG_ACPI
      64             : static bool drm_connector_acpi_bus_match(struct device *dev)
      65             : {
      66             :         return dev->type == &drm_sysfs_device_connector;
      67             : }
      68             : 
      69             : static struct acpi_device *drm_connector_acpi_find_companion(struct device *dev)
      70             : {
      71             :         struct drm_connector *connector = to_drm_connector(dev);
      72             : 
      73             :         return to_acpi_device_node(connector->fwnode);
      74             : }
      75             : 
      76             : static struct acpi_bus_type drm_connector_acpi_bus = {
      77             :         .name = "drm_connector",
      78             :         .match = drm_connector_acpi_bus_match,
      79             :         .find_companion = drm_connector_acpi_find_companion,
      80             : };
      81             : 
      82             : static void drm_sysfs_acpi_register(void)
      83             : {
      84             :         register_acpi_bus_type(&drm_connector_acpi_bus);
      85             : }
      86             : 
      87             : static void drm_sysfs_acpi_unregister(void)
      88             : {
      89             :         unregister_acpi_bus_type(&drm_connector_acpi_bus);
      90             : }
      91             : #else
      92             : static void drm_sysfs_acpi_register(void) { }
      93             : static void drm_sysfs_acpi_unregister(void) { }
      94             : #endif
      95             : 
      96           2 : static char *drm_devnode(const struct device *dev, umode_t *mode)
      97             : {
      98           2 :         return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
      99             : }
     100             : 
     101           0 : static int typec_connector_bind(struct device *dev,
     102             :                                 struct device *typec_connector, void *data)
     103             : {
     104             :         int ret;
     105             : 
     106           0 :         ret = sysfs_create_link(&dev->kobj, &typec_connector->kobj, "typec_connector");
     107           0 :         if (ret)
     108             :                 return ret;
     109             : 
     110           0 :         ret = sysfs_create_link(&typec_connector->kobj, &dev->kobj, "drm_connector");
     111           0 :         if (ret)
     112           0 :                 sysfs_remove_link(&dev->kobj, "typec_connector");
     113             : 
     114             :         return ret;
     115             : }
     116             : 
     117           0 : static void typec_connector_unbind(struct device *dev,
     118             :                                    struct device *typec_connector, void *data)
     119             : {
     120           0 :         sysfs_remove_link(&typec_connector->kobj, "drm_connector");
     121           0 :         sysfs_remove_link(&dev->kobj, "typec_connector");
     122           0 : }
     123             : 
     124             : static const struct component_ops typec_connector_ops = {
     125             :         .bind = typec_connector_bind,
     126             :         .unbind = typec_connector_unbind,
     127             : };
     128             : 
     129             : static CLASS_ATTR_STRING(version, S_IRUGO, "drm 1.1.0 20060810");
     130             : 
     131             : /**
     132             :  * drm_sysfs_init - initialize sysfs helpers
     133             :  *
     134             :  * This is used to create the DRM class, which is the implicit parent of any
     135             :  * other top-level DRM sysfs objects.
     136             :  *
     137             :  * You must call drm_sysfs_destroy() to release the allocated resources.
     138             :  *
     139             :  * Return: 0 on success, negative error code on failure.
     140             :  */
     141           1 : int drm_sysfs_init(void)
     142             : {
     143             :         int err;
     144             : 
     145           1 :         drm_class = class_create("drm");
     146           2 :         if (IS_ERR(drm_class))
     147           0 :                 return PTR_ERR(drm_class);
     148             : 
     149           2 :         err = class_create_file(drm_class, &class_attr_version.attr);
     150           1 :         if (err) {
     151           0 :                 class_destroy(drm_class);
     152           0 :                 drm_class = NULL;
     153           0 :                 return err;
     154             :         }
     155             : 
     156           1 :         drm_class->devnode = drm_devnode;
     157             : 
     158             :         drm_sysfs_acpi_register();
     159           1 :         return 0;
     160             : }
     161             : 
     162             : /**
     163             :  * drm_sysfs_destroy - destroys DRM class
     164             :  *
     165             :  * Destroy the DRM device class.
     166             :  */
     167           0 : void drm_sysfs_destroy(void)
     168             : {
     169           0 :         if (IS_ERR_OR_NULL(drm_class))
     170             :                 return;
     171             :         drm_sysfs_acpi_unregister();
     172           0 :         class_remove_file(drm_class, &class_attr_version.attr);
     173           0 :         class_destroy(drm_class);
     174           0 :         drm_class = NULL;
     175             : }
     176             : 
     177          22 : static void drm_sysfs_release(struct device *dev)
     178             : {
     179          22 :         kfree(dev);
     180          22 : }
     181             : 
     182             : /*
     183             :  * Connector properties
     184             :  */
     185           0 : static ssize_t status_store(struct device *device,
     186             :                            struct device_attribute *attr,
     187             :                            const char *buf, size_t count)
     188             : {
     189           0 :         struct drm_connector *connector = to_drm_connector(device);
     190           0 :         struct drm_device *dev = connector->dev;
     191             :         enum drm_connector_force old_force;
     192             :         int ret;
     193             : 
     194           0 :         ret = mutex_lock_interruptible(&dev->mode_config.mutex);
     195           0 :         if (ret)
     196           0 :                 return ret;
     197             : 
     198           0 :         old_force = connector->force;
     199             : 
     200           0 :         if (sysfs_streq(buf, "detect"))
     201           0 :                 connector->force = 0;
     202           0 :         else if (sysfs_streq(buf, "on"))
     203           0 :                 connector->force = DRM_FORCE_ON;
     204           0 :         else if (sysfs_streq(buf, "on-digital"))
     205           0 :                 connector->force = DRM_FORCE_ON_DIGITAL;
     206           0 :         else if (sysfs_streq(buf, "off"))
     207           0 :                 connector->force = DRM_FORCE_OFF;
     208             :         else
     209             :                 ret = -EINVAL;
     210             : 
     211           0 :         if (old_force != connector->force || !connector->force) {
     212           0 :                 DRM_DEBUG_KMS("[CONNECTOR:%d:%s] force updated from %d to %d or reprobing\n",
     213             :                               connector->base.id,
     214             :                               connector->name,
     215             :                               old_force, connector->force);
     216             : 
     217           0 :                 connector->funcs->fill_modes(connector,
     218           0 :                                              dev->mode_config.max_width,
     219           0 :                                              dev->mode_config.max_height);
     220             :         }
     221             : 
     222           0 :         mutex_unlock(&dev->mode_config.mutex);
     223             : 
     224           0 :         return ret ? ret : count;
     225             : }
     226             : 
     227           0 : static ssize_t status_show(struct device *device,
     228             :                            struct device_attribute *attr,
     229             :                            char *buf)
     230             : {
     231           0 :         struct drm_connector *connector = to_drm_connector(device);
     232             :         enum drm_connector_status status;
     233             : 
     234           0 :         status = READ_ONCE(connector->status);
     235             : 
     236           0 :         return sysfs_emit(buf, "%s\n",
     237             :                           drm_get_connector_status_name(status));
     238             : }
     239             : 
     240           0 : static ssize_t dpms_show(struct device *device,
     241             :                            struct device_attribute *attr,
     242             :                            char *buf)
     243             : {
     244           0 :         struct drm_connector *connector = to_drm_connector(device);
     245             :         int dpms;
     246             : 
     247           0 :         dpms = READ_ONCE(connector->dpms);
     248             : 
     249           0 :         return sysfs_emit(buf, "%s\n", drm_get_dpms_name(dpms));
     250             : }
     251             : 
     252           0 : static ssize_t enabled_show(struct device *device,
     253             :                             struct device_attribute *attr,
     254             :                            char *buf)
     255             : {
     256           0 :         struct drm_connector *connector = to_drm_connector(device);
     257             :         bool enabled;
     258             : 
     259           0 :         enabled = READ_ONCE(connector->encoder);
     260             : 
     261           0 :         return sysfs_emit(buf, enabled ? "enabled\n" : "disabled\n");
     262             : }
     263             : 
     264           0 : static ssize_t edid_show(struct file *filp, struct kobject *kobj,
     265             :                          struct bin_attribute *attr, char *buf, loff_t off,
     266             :                          size_t count)
     267             : {
     268           0 :         struct device *connector_dev = kobj_to_dev(kobj);
     269           0 :         struct drm_connector *connector = to_drm_connector(connector_dev);
     270             :         unsigned char *edid;
     271             :         size_t size;
     272           0 :         ssize_t ret = 0;
     273             : 
     274           0 :         mutex_lock(&connector->dev->mode_config.mutex);
     275           0 :         if (!connector->edid_blob_ptr)
     276             :                 goto unlock;
     277             : 
     278           0 :         edid = connector->edid_blob_ptr->data;
     279           0 :         size = connector->edid_blob_ptr->length;
     280           0 :         if (!edid)
     281             :                 goto unlock;
     282             : 
     283           0 :         if (off >= size)
     284             :                 goto unlock;
     285             : 
     286           0 :         if (off + count > size)
     287           0 :                 count = size - off;
     288           0 :         memcpy(buf, edid + off, count);
     289             : 
     290           0 :         ret = count;
     291             : unlock:
     292           0 :         mutex_unlock(&connector->dev->mode_config.mutex);
     293             : 
     294           0 :         return ret;
     295             : }
     296             : 
     297           0 : static ssize_t modes_show(struct device *device,
     298             :                            struct device_attribute *attr,
     299             :                            char *buf)
     300             : {
     301           0 :         struct drm_connector *connector = to_drm_connector(device);
     302             :         struct drm_display_mode *mode;
     303           0 :         int written = 0;
     304             : 
     305           0 :         mutex_lock(&connector->dev->mode_config.mutex);
     306           0 :         list_for_each_entry(mode, &connector->modes, head) {
     307           0 :                 written += scnprintf(buf + written, PAGE_SIZE - written, "%s\n",
     308           0 :                                     mode->name);
     309             :         }
     310           0 :         mutex_unlock(&connector->dev->mode_config.mutex);
     311             : 
     312           0 :         return written;
     313             : }
     314             : 
     315           0 : static ssize_t connector_id_show(struct device *device,
     316             :                                  struct device_attribute *attr,
     317             :                                  char *buf)
     318             : {
     319           0 :         struct drm_connector *connector = to_drm_connector(device);
     320             : 
     321           0 :         return sysfs_emit(buf, "%d\n", connector->base.id);
     322             : }
     323             : 
     324             : static DEVICE_ATTR_RW(status);
     325             : static DEVICE_ATTR_RO(enabled);
     326             : static DEVICE_ATTR_RO(dpms);
     327             : static DEVICE_ATTR_RO(modes);
     328             : static DEVICE_ATTR_RO(connector_id);
     329             : 
     330             : static struct attribute *connector_dev_attrs[] = {
     331             :         &dev_attr_status.attr,
     332             :         &dev_attr_enabled.attr,
     333             :         &dev_attr_dpms.attr,
     334             :         &dev_attr_modes.attr,
     335             :         &dev_attr_connector_id.attr,
     336             :         NULL
     337             : };
     338             : 
     339             : static struct bin_attribute edid_attr = {
     340             :         .attr.name = "edid",
     341             :         .attr.mode = 0444,
     342             :         .size = 0,
     343             :         .read = edid_show,
     344             : };
     345             : 
     346             : static struct bin_attribute *connector_bin_attrs[] = {
     347             :         &edid_attr,
     348             :         NULL
     349             : };
     350             : 
     351             : static const struct attribute_group connector_dev_group = {
     352             :         .attrs = connector_dev_attrs,
     353             :         .bin_attrs = connector_bin_attrs,
     354             : };
     355             : 
     356             : static const struct attribute_group *connector_dev_groups[] = {
     357             :         &connector_dev_group,
     358             :         NULL
     359             : };
     360             : 
     361           0 : int drm_sysfs_connector_add(struct drm_connector *connector)
     362             : {
     363           0 :         struct drm_device *dev = connector->dev;
     364             :         struct device *kdev;
     365             :         int r;
     366             : 
     367           0 :         if (connector->kdev)
     368             :                 return 0;
     369             : 
     370           0 :         kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
     371           0 :         if (!kdev)
     372             :                 return -ENOMEM;
     373             : 
     374           0 :         device_initialize(kdev);
     375           0 :         kdev->class = drm_class;
     376           0 :         kdev->type = &drm_sysfs_device_connector;
     377           0 :         kdev->parent = dev->primary->kdev;
     378           0 :         kdev->groups = connector_dev_groups;
     379           0 :         kdev->release = drm_sysfs_release;
     380           0 :         dev_set_drvdata(kdev, connector);
     381             : 
     382           0 :         r = dev_set_name(kdev, "card%d-%s", dev->primary->index, connector->name);
     383           0 :         if (r)
     384             :                 goto err_free;
     385             : 
     386           0 :         DRM_DEBUG("adding \"%s\" to sysfs\n",
     387             :                   connector->name);
     388             : 
     389           0 :         r = device_add(kdev);
     390           0 :         if (r) {
     391           0 :                 drm_err(dev, "failed to register connector device: %d\n", r);
     392           0 :                 goto err_free;
     393             :         }
     394             : 
     395           0 :         connector->kdev = kdev;
     396             : 
     397           0 :         if (dev_fwnode(kdev)) {
     398           0 :                 r = component_add(kdev, &typec_connector_ops);
     399           0 :                 if (r)
     400           0 :                         drm_err(dev, "failed to add component to create link to typec connector\n");
     401             :         }
     402             : 
     403           0 :         if (connector->ddc)
     404           0 :                 return sysfs_create_link(&connector->kdev->kobj,
     405             :                                  &connector->ddc->dev.kobj, "ddc");
     406             : 
     407             :         return 0;
     408             : 
     409             : err_free:
     410           0 :         put_device(kdev);
     411           0 :         return r;
     412             : }
     413             : 
     414           0 : void drm_sysfs_connector_remove(struct drm_connector *connector)
     415             : {
     416           0 :         if (!connector->kdev)
     417             :                 return;
     418             : 
     419           0 :         if (connector->ddc)
     420           0 :                 sysfs_remove_link(&connector->kdev->kobj, "ddc");
     421             : 
     422           0 :         if (dev_fwnode(connector->kdev))
     423           0 :                 component_del(connector->kdev, &typec_connector_ops);
     424             : 
     425           0 :         DRM_DEBUG("removing \"%s\" from sysfs\n",
     426             :                   connector->name);
     427             : 
     428           0 :         device_unregister(connector->kdev);
     429           0 :         connector->kdev = NULL;
     430             : }
     431             : 
     432           0 : void drm_sysfs_lease_event(struct drm_device *dev)
     433             : {
     434           0 :         char *event_string = "LEASE=1";
     435           0 :         char *envp[] = { event_string, NULL };
     436             : 
     437           0 :         DRM_DEBUG("generating lease event\n");
     438             : 
     439           0 :         kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
     440           0 : }
     441             : 
     442             : /**
     443             :  * drm_sysfs_hotplug_event - generate a DRM uevent
     444             :  * @dev: DRM device
     445             :  *
     446             :  * Send a uevent for the DRM device specified by @dev.  Currently we only
     447             :  * set HOTPLUG=1 in the uevent environment, but this could be expanded to
     448             :  * deal with other types of events.
     449             :  *
     450             :  * Any new uapi should be using the drm_sysfs_connector_status_event()
     451             :  * for uevents on connector status change.
     452             :  */
     453           0 : void drm_sysfs_hotplug_event(struct drm_device *dev)
     454             : {
     455           0 :         char *event_string = "HOTPLUG=1";
     456           0 :         char *envp[] = { event_string, NULL };
     457             : 
     458           0 :         DRM_DEBUG("generating hotplug event\n");
     459             : 
     460           0 :         kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
     461           0 : }
     462             : EXPORT_SYMBOL(drm_sysfs_hotplug_event);
     463             : 
     464             : /**
     465             :  * drm_sysfs_connector_hotplug_event - generate a DRM uevent for any connector
     466             :  * change
     467             :  * @connector: connector which has changed
     468             :  *
     469             :  * Send a uevent for the DRM connector specified by @connector. This will send
     470             :  * a uevent with the properties HOTPLUG=1 and CONNECTOR.
     471             :  */
     472           0 : void drm_sysfs_connector_hotplug_event(struct drm_connector *connector)
     473             : {
     474           0 :         struct drm_device *dev = connector->dev;
     475           0 :         char hotplug_str[] = "HOTPLUG=1", conn_id[21];
     476           0 :         char *envp[] = { hotplug_str, conn_id, NULL };
     477             : 
     478           0 :         snprintf(conn_id, sizeof(conn_id),
     479             :                  "CONNECTOR=%u", connector->base.id);
     480             : 
     481           0 :         drm_dbg_kms(connector->dev,
     482             :                     "[CONNECTOR:%d:%s] generating connector hotplug event\n",
     483             :                     connector->base.id, connector->name);
     484             : 
     485           0 :         kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
     486           0 : }
     487             : EXPORT_SYMBOL(drm_sysfs_connector_hotplug_event);
     488             : 
     489             : /**
     490             :  * drm_sysfs_connector_property_event - generate a DRM uevent for connector
     491             :  * property change
     492             :  * @connector: connector on which property changed
     493             :  * @property: connector property which has changed.
     494             :  *
     495             :  * Send a uevent for the specified DRM connector and property.  Currently we
     496             :  * set HOTPLUG=1 and connector id along with the attached property id
     497             :  * related to the change.
     498             :  */
     499           0 : void drm_sysfs_connector_property_event(struct drm_connector *connector,
     500             :                                         struct drm_property *property)
     501             : {
     502           0 :         struct drm_device *dev = connector->dev;
     503           0 :         char hotplug_str[] = "HOTPLUG=1", conn_id[21], prop_id[21];
     504           0 :         char *envp[4] = { hotplug_str, conn_id, prop_id, NULL };
     505             : 
     506           0 :         WARN_ON(!drm_mode_obj_find_prop_id(&connector->base,
     507             :                                            property->base.id));
     508             : 
     509           0 :         snprintf(conn_id, ARRAY_SIZE(conn_id),
     510             :                  "CONNECTOR=%u", connector->base.id);
     511           0 :         snprintf(prop_id, ARRAY_SIZE(prop_id),
     512             :                  "PROPERTY=%u", property->base.id);
     513             : 
     514           0 :         drm_dbg_kms(connector->dev,
     515             :                     "[CONNECTOR:%d:%s] generating connector property event for [PROP:%d:%s]\n",
     516             :                     connector->base.id, connector->name,
     517             :                     property->base.id, property->name);
     518             : 
     519           0 :         kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
     520           0 : }
     521             : EXPORT_SYMBOL(drm_sysfs_connector_property_event);
     522             : 
     523          22 : struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
     524             : {
     525             :         const char *minor_str;
     526             :         struct device *kdev;
     527             :         int r;
     528             : 
     529          22 :         kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
     530          22 :         if (!kdev)
     531             :                 return ERR_PTR(-ENOMEM);
     532             : 
     533          22 :         device_initialize(kdev);
     534             : 
     535          22 :         if (minor->type == DRM_MINOR_ACCEL) {
     536             :                 minor_str = "accel%d";
     537             :                 accel_set_device_instance_params(kdev, minor->index);
     538             :         } else {
     539          22 :                 if (minor->type == DRM_MINOR_RENDER)
     540             :                         minor_str = "renderD%d";
     541             :                 else
     542          22 :                         minor_str = "card%d";
     543             : 
     544          22 :                 kdev->devt = MKDEV(DRM_MAJOR, minor->index);
     545          22 :                 kdev->class = drm_class;
     546          22 :                 kdev->type = &drm_sysfs_device_minor;
     547             :         }
     548             : 
     549          22 :         kdev->parent = minor->dev->dev;
     550          22 :         kdev->release = drm_sysfs_release;
     551          44 :         dev_set_drvdata(kdev, minor);
     552             : 
     553          22 :         r = dev_set_name(kdev, minor_str, minor->index);
     554          22 :         if (r < 0)
     555             :                 goto err_free;
     556             : 
     557             :         return kdev;
     558             : 
     559             : err_free:
     560           0 :         put_device(kdev);
     561           0 :         return ERR_PTR(r);
     562             : }
     563             : 
     564             : /**
     565             :  * drm_class_device_register - register new device with the DRM sysfs class
     566             :  * @dev: device to register
     567             :  *
     568             :  * Registers a new &struct device within the DRM sysfs class. Essentially only
     569             :  * used by ttm to have a place for its global settings. Drivers should never use
     570             :  * this.
     571             :  */
     572           0 : int drm_class_device_register(struct device *dev)
     573             : {
     574           0 :         if (!drm_class || IS_ERR(drm_class))
     575             :                 return -ENOENT;
     576             : 
     577           0 :         dev->class = drm_class;
     578           0 :         return device_register(dev);
     579             : }
     580             : EXPORT_SYMBOL_GPL(drm_class_device_register);
     581             : 
     582             : /**
     583             :  * drm_class_device_unregister - unregister device with the DRM sysfs class
     584             :  * @dev: device to unregister
     585             :  *
     586             :  * Unregisters a &struct device from the DRM sysfs class. Essentially only used
     587             :  * by ttm to have a place for its global settings. Drivers should never use
     588             :  * this.
     589             :  */
     590           0 : void drm_class_device_unregister(struct device *dev)
     591             : {
     592           0 :         return device_unregister(dev);
     593             : }
     594             : EXPORT_SYMBOL_GPL(drm_class_device_unregister);

Generated by: LCOV version 1.14