Commit d723091c authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'driver-core-7.0-rc5' of...

Merge tag 'driver-core-7.0-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core

Pull driver core fixes from Danilo Krummrich:

 - Generalize driver_override in the driver core, providing a common
   sysfs implementation and concurrency-safe accessors for bus
   implementations

 - Do not use driver_override as IRQ name in the hwmon axi-fan driver

 - Remove an unnecessary driver_override check in sh platform_early

 - Migrate the platform bus to use the generic driver_override
   infrastructure, fixing a UAF condition caused by accessing the
   driver_override field without proper locking in the platform_match()
   callback

* tag 'driver-core-7.0-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core:
  driver core: platform: use generic driver_override infrastructure
  sh: platform_early: remove pdev->driver_override check
  hwmon: axi-fan: don't use driver_override as IRQ name
  docs: driver-model: document driver_override
  driver core: generalize driver_override in struct device
parents 113ae7b4 2b38efc0
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -99,3 +99,51 @@ of the driver is decremented. All symlinks between the two are removed.
When a driver is removed, the list of devices that it supports is
iterated over, and the driver's remove callback is called for each
one. The device is removed from that list and the symlinks removed.


Driver Override
~~~~~~~~~~~~~~~

Userspace may override the standard matching by writing a driver name to
a device's ``driver_override`` sysfs attribute.  When set, only a driver
whose name matches the override will be considered during binding.  This
bypasses all bus-specific matching (OF, ACPI, ID tables, etc.).

The override may be cleared by writing an empty string, which returns
the device to standard matching rules.  Writing to ``driver_override``
does not automatically unbind the device from its current driver or
make any attempt to load the specified driver.

Buses opt into this mechanism by setting the ``driver_override`` flag in
their ``struct bus_type``::

  const struct bus_type example_bus_type = {
      ...
      .driver_override = true,
  };

When the flag is set, the driver core automatically creates the
``driver_override`` sysfs attribute for every device on that bus.

The bus's ``match()`` callback should check the override before performing
its own matching, using ``device_match_driver_override()``::

  static int example_match(struct device *dev, const struct device_driver *drv)
  {
      int ret;

      ret = device_match_driver_override(dev, drv);
      if (ret >= 0)
          return ret;

      /* Fall through to bus-specific matching... */
  }

``device_match_driver_override()`` returns > 0 if the override matches
the given driver, 0 if the override is set but does not match, or < 0 if
no override is set at all.

Additional helpers are available:

- ``device_set_driver_override()`` - set or clear the override from kernel code.
- ``device_has_driver_override()`` - check whether an override is set.
+0 −4
Original line number Diff line number Diff line
@@ -26,10 +26,6 @@ static int platform_match(struct device *dev, struct device_driver *drv)
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;
+42 −1
Original line number Diff line number Diff line
@@ -504,6 +504,36 @@ int bus_for_each_drv(const struct bus_type *bus, struct device_driver *start,
}
EXPORT_SYMBOL_GPL(bus_for_each_drv);

static ssize_t driver_override_store(struct device *dev,
				     struct device_attribute *attr,
				     const char *buf, size_t count)
{
	int ret;

	ret = __device_set_driver_override(dev, buf, count);
	if (ret)
		return ret;

	return count;
}

static ssize_t driver_override_show(struct device *dev,
				    struct device_attribute *attr, char *buf)
{
	guard(spinlock)(&dev->driver_override.lock);
	return sysfs_emit(buf, "%s\n", dev->driver_override.name);
}
static DEVICE_ATTR_RW(driver_override);

static struct attribute *driver_override_dev_attrs[] = {
	&dev_attr_driver_override.attr,
	NULL,
};

static const struct attribute_group driver_override_dev_group = {
	.attrs = driver_override_dev_attrs,
};

/**
 * bus_add_device - add device to bus
 * @dev: device being added
@@ -537,9 +567,15 @@ int bus_add_device(struct device *dev)
	if (error)
		goto out_put;

	error = sysfs_create_link(&sp->devices_kset->kobj, &dev->kobj, dev_name(dev));
	if (dev->bus->driver_override) {
		error = device_add_group(dev, &driver_override_dev_group);
		if (error)
			goto out_groups;
	}

	error = sysfs_create_link(&sp->devices_kset->kobj, &dev->kobj, dev_name(dev));
	if (error)
		goto out_override;

	error = sysfs_create_link(&dev->kobj, &sp->subsys.kobj, "subsystem");
	if (error)
@@ -550,6 +586,9 @@ int bus_add_device(struct device *dev)

out_subsys:
	sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev));
out_override:
	if (dev->bus->driver_override)
		device_remove_group(dev, &driver_override_dev_group);
out_groups:
	device_remove_groups(dev, sp->bus->dev_groups);
out_put:
@@ -607,6 +646,8 @@ void bus_remove_device(struct device *dev)

	sysfs_remove_link(&dev->kobj, "subsystem");
	sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev));
	if (dev->bus->driver_override)
		device_remove_group(dev, &driver_override_dev_group);
	device_remove_groups(dev, dev->bus->dev_groups);
	if (klist_node_attached(&dev->p->knode_bus))
		klist_del(&dev->p->knode_bus);
+2 −0
Original line number Diff line number Diff line
@@ -2556,6 +2556,7 @@ static void device_release(struct kobject *kobj)
	devres_release_all(dev);

	kfree(dev->dma_range_map);
	kfree(dev->driver_override.name);

	if (dev->release)
		dev->release(dev);
@@ -3159,6 +3160,7 @@ void device_initialize(struct device *dev)
	kobject_init(&dev->kobj, &device_ktype);
	INIT_LIST_HEAD(&dev->dma_pools);
	mutex_init(&dev->mutex);
	spin_lock_init(&dev->driver_override.lock);
	lockdep_set_novalidate_class(&dev->mutex);
	spin_lock_init(&dev->devres_lock);
	INIT_LIST_HEAD(&dev->devres_head);
+60 −0
Original line number Diff line number Diff line
@@ -381,6 +381,66 @@ static void __exit deferred_probe_exit(void)
}
__exitcall(deferred_probe_exit);

int __device_set_driver_override(struct device *dev, const char *s, size_t len)
{
	const char *new, *old;
	char *cp;

	if (!s)
		return -EINVAL;

	/*
	 * The stored value will be used in sysfs show callback (sysfs_emit()),
	 * which has a length limit of PAGE_SIZE and adds a trailing newline.
	 * Thus we can store one character less to avoid truncation during sysfs
	 * show.
	 */
	if (len >= (PAGE_SIZE - 1))
		return -EINVAL;

	/*
	 * Compute the real length of the string in case userspace sends us a
	 * bunch of \0 characters like python likes to do.
	 */
	len = strlen(s);

	if (!len) {
		/* Empty string passed - clear override */
		spin_lock(&dev->driver_override.lock);
		old = dev->driver_override.name;
		dev->driver_override.name = NULL;
		spin_unlock(&dev->driver_override.lock);
		kfree(old);

		return 0;
	}

	cp = strnchr(s, len, '\n');
	if (cp)
		len = cp - s;

	new = kstrndup(s, len, GFP_KERNEL);
	if (!new)
		return -ENOMEM;

	spin_lock(&dev->driver_override.lock);
	old = dev->driver_override.name;
	if (cp != s) {
		dev->driver_override.name = new;
		spin_unlock(&dev->driver_override.lock);
	} else {
		/* "\n" passed - clear override */
		dev->driver_override.name = NULL;
		spin_unlock(&dev->driver_override.lock);

		kfree(new);
	}
	kfree(old);

	return 0;
}
EXPORT_SYMBOL_GPL(__device_set_driver_override);

/**
 * device_is_bound() - Check if device is bound to a driver
 * @dev: device to check
Loading