diff options
author | Kim Phillips <kim.phillips@freescale.com> | 2014-06-02 20:42:58 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-07-08 18:31:26 -0400 |
commit | 3d713e0e382e6fcfb4bba1501645b66c129ad60b (patch) | |
tree | 9b47c38f09df44537b163ca18eabc8c979aa1f04 | |
parent | 1cec24c59b4a133fc9c83912996168f511075485 (diff) |
driver core: platform: add device binding path 'driver_override'
Needed by platform device drivers, such as the upcoming
vfio-platform driver, in order to bypass the existing OF, ACPI,
id_table and name string matches, and successfully be able to be
bound to any device, like so:
echo vfio-platform > /sys/bus/platform/devices/fff51000.ethernet/driver_override
echo fff51000.ethernet > /sys/bus/platform/devices/fff51000.ethernet/driver/unbind
echo fff51000.ethernet > /sys/bus/platform/drivers_probe
This mimics "PCI: Introduce new device binding path using
pci_dev.driver_override", which is an interface enhancement
for more deterministic PCI device binding, e.g., when in the
presence of hotplug.
Reviewed-by: Alex Williamson <alex.williamson@redhat.com>
Reviewed-by: Alexander Graf <agraf@suse.de>
Reviewed-by: Stuart Yoder <stuart.yoder@freescale.com>
Signed-off-by: Kim Phillips <kim.phillips@freescale.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | Documentation/ABI/testing/sysfs-bus-platform | 20 | ||||
-rw-r--r-- | drivers/base/platform.c | 47 | ||||
-rw-r--r-- | include/linux/platform_device.h | 1 |
3 files changed, 68 insertions, 0 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-platform b/Documentation/ABI/testing/sysfs-bus-platform new file mode 100644 index 000000000000..5172a6124b27 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-platform | |||
@@ -0,0 +1,20 @@ | |||
1 | What: /sys/bus/platform/devices/.../driver_override | ||
2 | Date: April 2014 | ||
3 | Contact: Kim Phillips <kim.phillips@freescale.com> | ||
4 | Description: | ||
5 | This file allows the driver for a device to be specified which | ||
6 | will override standard OF, ACPI, ID table, and name matching. | ||
7 | When specified, only a driver with a name matching the value | ||
8 | written to driver_override will have an opportunity to bind | ||
9 | to the device. The override is specified by writing a string | ||
10 | to the driver_override file (echo vfio-platform > \ | ||
11 | driver_override) and may be cleared with an empty string | ||
12 | (echo > driver_override). This returns the device to standard | ||
13 | matching rules binding. Writing to driver_override does not | ||
14 | automatically unbind the device from its current driver or make | ||
15 | any attempt to automatically load the specified driver. If no | ||
16 | driver with a matching name is currently loaded in the kernel, | ||
17 | the device will not bind to any driver. This also allows | ||
18 | devices to opt-out of driver binding using a driver_override | ||
19 | name such as "none". Only a single driver may be specified in | ||
20 | the override, there is no support for parsing delimiters. | ||
diff --git a/drivers/base/platform.c b/drivers/base/platform.c index c48c4acb9b87..148f66a1d49a 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <linux/pm_runtime.h> | 23 | #include <linux/pm_runtime.h> |
24 | #include <linux/idr.h> | 24 | #include <linux/idr.h> |
25 | #include <linux/acpi.h> | 25 | #include <linux/acpi.h> |
26 | #include <linux/limits.h> | ||
26 | 27 | ||
27 | #include "base.h" | 28 | #include "base.h" |
28 | #include "power/power.h" | 29 | #include "power/power.h" |
@@ -191,6 +192,7 @@ static void platform_device_release(struct device *dev) | |||
191 | kfree(pa->pdev.dev.platform_data); | 192 | kfree(pa->pdev.dev.platform_data); |
192 | kfree(pa->pdev.mfd_cell); | 193 | kfree(pa->pdev.mfd_cell); |
193 | kfree(pa->pdev.resource); | 194 | kfree(pa->pdev.resource); |
195 | kfree(pa->pdev.driver_override); | ||
194 | kfree(pa); | 196 | kfree(pa); |
195 | } | 197 | } |
196 | 198 | ||
@@ -698,8 +700,49 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *a, | |||
698 | } | 700 | } |
699 | static DEVICE_ATTR_RO(modalias); | 701 | static DEVICE_ATTR_RO(modalias); |
700 | 702 | ||
703 | static ssize_t driver_override_store(struct device *dev, | ||
704 | struct device_attribute *attr, | ||
705 | const char *buf, size_t count) | ||
706 | { | ||
707 | struct platform_device *pdev = to_platform_device(dev); | ||
708 | char *driver_override, *old = pdev->driver_override, *cp; | ||
709 | |||
710 | if (count > PATH_MAX) | ||
711 | return -EINVAL; | ||
712 | |||
713 | driver_override = kstrndup(buf, count, GFP_KERNEL); | ||
714 | if (!driver_override) | ||
715 | return -ENOMEM; | ||
716 | |||
717 | cp = strchr(driver_override, '\n'); | ||
718 | if (cp) | ||
719 | *cp = '\0'; | ||
720 | |||
721 | if (strlen(driver_override)) { | ||
722 | pdev->driver_override = driver_override; | ||
723 | } else { | ||
724 | kfree(driver_override); | ||
725 | pdev->driver_override = NULL; | ||
726 | } | ||
727 | |||
728 | kfree(old); | ||
729 | |||
730 | return count; | ||
731 | } | ||
732 | |||
733 | static ssize_t driver_override_show(struct device *dev, | ||
734 | struct device_attribute *attr, char *buf) | ||
735 | { | ||
736 | struct platform_device *pdev = to_platform_device(dev); | ||
737 | |||
738 | return sprintf(buf, "%s\n", pdev->driver_override); | ||
739 | } | ||
740 | static DEVICE_ATTR_RW(driver_override); | ||
741 | |||
742 | |||
701 | static struct attribute *platform_dev_attrs[] = { | 743 | static struct attribute *platform_dev_attrs[] = { |
702 | &dev_attr_modalias.attr, | 744 | &dev_attr_modalias.attr, |
745 | &dev_attr_driver_override.attr, | ||
703 | NULL, | 746 | NULL, |
704 | }; | 747 | }; |
705 | ATTRIBUTE_GROUPS(platform_dev); | 748 | ATTRIBUTE_GROUPS(platform_dev); |
@@ -755,6 +798,10 @@ static int platform_match(struct device *dev, struct device_driver *drv) | |||
755 | struct platform_device *pdev = to_platform_device(dev); | 798 | struct platform_device *pdev = to_platform_device(dev); |
756 | struct platform_driver *pdrv = to_platform_driver(drv); | 799 | struct platform_driver *pdrv = to_platform_driver(drv); |
757 | 800 | ||
801 | /* When driver_override is set, only bind to the matching driver */ | ||
802 | if (pdev->driver_override) | ||
803 | return !strcmp(pdev->driver_override, drv->name); | ||
804 | |||
758 | /* Attempt an OF style match first */ | 805 | /* Attempt an OF style match first */ |
759 | if (of_driver_match_device(dev, drv)) | 806 | if (of_driver_match_device(dev, drv)) |
760 | return 1; | 807 | return 1; |
diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 16f6654082dd..153d303af7eb 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h | |||
@@ -28,6 +28,7 @@ struct platform_device { | |||
28 | struct resource *resource; | 28 | struct resource *resource; |
29 | 29 | ||
30 | const struct platform_device_id *id_entry; | 30 | const struct platform_device_id *id_entry; |
31 | char *driver_override; /* Driver name to force a match */ | ||
31 | 32 | ||
32 | /* MFD cell pointer */ | 33 | /* MFD cell pointer */ |
33 | struct mfd_cell *mfd_cell; | 34 | struct mfd_cell *mfd_cell; |