-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
drivers: move the early platform device support to arch/sh
SuperH is the only user of the current implementation of early platform device support. We want to introduce a more robust approach to early probing. As the first step - move all the current early platform code to arch/sh. In order not to export internal drivers/base functions to arch code for this temporary solution - copy the two needed routines for driver matching from drivers/base/platform.c to arch/sh/drivers/platform_early.c. Also: call early_platform_cleanup() from subsys_initcall() so that it's called after all early devices are probed. Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com> Cc: Rich Felker <dalias@libc.org> Link: https://lore.kernel.org/r/20191003092913.10731-2-brgl@bgdev.pl Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
- Loading branch information
Bartosz Golaszewski
authored and
Greg Kroah-Hartman
committed
Oct 7, 2019
1 parent
d4387cd
commit 507fd01
Showing
40 changed files
with
480 additions
and
342 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,347 @@ | ||
// SPDX--License-Identifier: GPL-2.0 | ||
|
||
#include <asm/platform_early.h> | ||
#include <linux/mod_devicetable.h> | ||
#include <linux/pm.h> | ||
|
||
static __initdata LIST_HEAD(early_platform_driver_list); | ||
static __initdata LIST_HEAD(early_platform_device_list); | ||
|
||
static const struct platform_device_id * | ||
platform_match_id(const struct platform_device_id *id, | ||
struct platform_device *pdev) | ||
{ | ||
while (id->name[0]) { | ||
if (strcmp(pdev->name, id->name) == 0) { | ||
pdev->id_entry = id; | ||
return id; | ||
} | ||
id++; | ||
} | ||
return NULL; | ||
} | ||
|
||
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; | ||
|
||
/* fall-back to driver name match */ | ||
return (strcmp(pdev->name, drv->name) == 0); | ||
} | ||
|
||
#ifdef CONFIG_PM | ||
static void device_pm_init_common(struct device *dev) | ||
{ | ||
if (!dev->power.early_init) { | ||
spin_lock_init(&dev->power.lock); | ||
dev->power.qos = NULL; | ||
dev->power.early_init = true; | ||
} | ||
} | ||
|
||
static void pm_runtime_early_init(struct device *dev) | ||
{ | ||
dev->power.disable_depth = 1; | ||
device_pm_init_common(dev); | ||
} | ||
#else | ||
static void pm_runtime_early_init(struct device *dev) {} | ||
#endif | ||
|
||
/** | ||
* early_platform_driver_register - register early platform driver | ||
* @epdrv: early_platform driver structure | ||
* @buf: string passed from early_param() | ||
* | ||
* Helper function for early_platform_init() / early_platform_init_buffer() | ||
*/ | ||
int __init early_platform_driver_register(struct early_platform_driver *epdrv, | ||
char *buf) | ||
{ | ||
char *tmp; | ||
int n; | ||
|
||
/* Simply add the driver to the end of the global list. | ||
* Drivers will by default be put on the list in compiled-in order. | ||
*/ | ||
if (!epdrv->list.next) { | ||
INIT_LIST_HEAD(&epdrv->list); | ||
list_add_tail(&epdrv->list, &early_platform_driver_list); | ||
} | ||
|
||
/* If the user has specified device then make sure the driver | ||
* gets prioritized. The driver of the last device specified on | ||
* command line will be put first on the list. | ||
*/ | ||
n = strlen(epdrv->pdrv->driver.name); | ||
if (buf && !strncmp(buf, epdrv->pdrv->driver.name, n)) { | ||
list_move(&epdrv->list, &early_platform_driver_list); | ||
|
||
/* Allow passing parameters after device name */ | ||
if (buf[n] == '\0' || buf[n] == ',') | ||
epdrv->requested_id = -1; | ||
else { | ||
epdrv->requested_id = simple_strtoul(&buf[n + 1], | ||
&tmp, 10); | ||
|
||
if (buf[n] != '.' || (tmp == &buf[n + 1])) { | ||
epdrv->requested_id = EARLY_PLATFORM_ID_ERROR; | ||
n = 0; | ||
} else | ||
n += strcspn(&buf[n + 1], ",") + 1; | ||
} | ||
|
||
if (buf[n] == ',') | ||
n++; | ||
|
||
if (epdrv->bufsize) { | ||
memcpy(epdrv->buffer, &buf[n], | ||
min_t(int, epdrv->bufsize, strlen(&buf[n]) + 1)); | ||
epdrv->buffer[epdrv->bufsize - 1] = '\0'; | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
/** | ||
* early_platform_add_devices - adds a number of early platform devices | ||
* @devs: array of early platform devices to add | ||
* @num: number of early platform devices in array | ||
* | ||
* Used by early architecture code to register early platform devices and | ||
* their platform data. | ||
*/ | ||
void __init early_platform_add_devices(struct platform_device **devs, int num) | ||
{ | ||
struct device *dev; | ||
int i; | ||
|
||
/* simply add the devices to list */ | ||
for (i = 0; i < num; i++) { | ||
dev = &devs[i]->dev; | ||
|
||
if (!dev->devres_head.next) { | ||
pm_runtime_early_init(dev); | ||
INIT_LIST_HEAD(&dev->devres_head); | ||
list_add_tail(&dev->devres_head, | ||
&early_platform_device_list); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* early_platform_driver_register_all - register early platform drivers | ||
* @class_str: string to identify early platform driver class | ||
* | ||
* Used by architecture code to register all early platform drivers | ||
* for a certain class. If omitted then only early platform drivers | ||
* with matching kernel command line class parameters will be registered. | ||
*/ | ||
void __init early_platform_driver_register_all(char *class_str) | ||
{ | ||
/* The "class_str" parameter may or may not be present on the kernel | ||
* command line. If it is present then there may be more than one | ||
* matching parameter. | ||
* | ||
* Since we register our early platform drivers using early_param() | ||
* we need to make sure that they also get registered in the case | ||
* when the parameter is missing from the kernel command line. | ||
* | ||
* We use parse_early_options() to make sure the early_param() gets | ||
* called at least once. The early_param() may be called more than | ||
* once since the name of the preferred device may be specified on | ||
* the kernel command line. early_platform_driver_register() handles | ||
* this case for us. | ||
*/ | ||
parse_early_options(class_str); | ||
} | ||
|
||
/** | ||
* early_platform_match - find early platform device matching driver | ||
* @epdrv: early platform driver structure | ||
* @id: id to match against | ||
*/ | ||
static struct platform_device * __init | ||
early_platform_match(struct early_platform_driver *epdrv, int id) | ||
{ | ||
struct platform_device *pd; | ||
|
||
list_for_each_entry(pd, &early_platform_device_list, dev.devres_head) | ||
if (platform_match(&pd->dev, &epdrv->pdrv->driver)) | ||
if (pd->id == id) | ||
return pd; | ||
|
||
return NULL; | ||
} | ||
|
||
/** | ||
* early_platform_left - check if early platform driver has matching devices | ||
* @epdrv: early platform driver structure | ||
* @id: return true if id or above exists | ||
*/ | ||
static int __init early_platform_left(struct early_platform_driver *epdrv, | ||
int id) | ||
{ | ||
struct platform_device *pd; | ||
|
||
list_for_each_entry(pd, &early_platform_device_list, dev.devres_head) | ||
if (platform_match(&pd->dev, &epdrv->pdrv->driver)) | ||
if (pd->id >= id) | ||
return 1; | ||
|
||
return 0; | ||
} | ||
|
||
/** | ||
* early_platform_driver_probe_id - probe drivers matching class_str and id | ||
* @class_str: string to identify early platform driver class | ||
* @id: id to match against | ||
* @nr_probe: number of platform devices to successfully probe before exiting | ||
*/ | ||
static int __init early_platform_driver_probe_id(char *class_str, | ||
int id, | ||
int nr_probe) | ||
{ | ||
struct early_platform_driver *epdrv; | ||
struct platform_device *match; | ||
int match_id; | ||
int n = 0; | ||
int left = 0; | ||
|
||
list_for_each_entry(epdrv, &early_platform_driver_list, list) { | ||
/* only use drivers matching our class_str */ | ||
if (strcmp(class_str, epdrv->class_str)) | ||
continue; | ||
|
||
if (id == -2) { | ||
match_id = epdrv->requested_id; | ||
left = 1; | ||
|
||
} else { | ||
match_id = id; | ||
left += early_platform_left(epdrv, id); | ||
|
||
/* skip requested id */ | ||
switch (epdrv->requested_id) { | ||
case EARLY_PLATFORM_ID_ERROR: | ||
case EARLY_PLATFORM_ID_UNSET: | ||
break; | ||
default: | ||
if (epdrv->requested_id == id) | ||
match_id = EARLY_PLATFORM_ID_UNSET; | ||
} | ||
} | ||
|
||
switch (match_id) { | ||
case EARLY_PLATFORM_ID_ERROR: | ||
pr_warn("%s: unable to parse %s parameter\n", | ||
class_str, epdrv->pdrv->driver.name); | ||
/* fall-through */ | ||
case EARLY_PLATFORM_ID_UNSET: | ||
match = NULL; | ||
break; | ||
default: | ||
match = early_platform_match(epdrv, match_id); | ||
} | ||
|
||
if (match) { | ||
/* | ||
* Set up a sensible init_name to enable | ||
* dev_name() and others to be used before the | ||
* rest of the driver core is initialized. | ||
*/ | ||
if (!match->dev.init_name && slab_is_available()) { | ||
if (match->id != -1) | ||
match->dev.init_name = | ||
kasprintf(GFP_KERNEL, "%s.%d", | ||
match->name, | ||
match->id); | ||
else | ||
match->dev.init_name = | ||
kasprintf(GFP_KERNEL, "%s", | ||
match->name); | ||
|
||
if (!match->dev.init_name) | ||
return -ENOMEM; | ||
} | ||
|
||
if (epdrv->pdrv->probe(match)) | ||
pr_warn("%s: unable to probe %s early.\n", | ||
class_str, match->name); | ||
else | ||
n++; | ||
} | ||
|
||
if (n >= nr_probe) | ||
break; | ||
} | ||
|
||
if (left) | ||
return n; | ||
else | ||
return -ENODEV; | ||
} | ||
|
||
/** | ||
* early_platform_driver_probe - probe a class of registered drivers | ||
* @class_str: string to identify early platform driver class | ||
* @nr_probe: number of platform devices to successfully probe before exiting | ||
* @user_only: only probe user specified early platform devices | ||
* | ||
* Used by architecture code to probe registered early platform drivers | ||
* within a certain class. For probe to happen a registered early platform | ||
* device matching a registered early platform driver is needed. | ||
*/ | ||
int __init early_platform_driver_probe(char *class_str, | ||
int nr_probe, | ||
int user_only) | ||
{ | ||
int k, n, i; | ||
|
||
n = 0; | ||
for (i = -2; n < nr_probe; i++) { | ||
k = early_platform_driver_probe_id(class_str, i, nr_probe - n); | ||
|
||
if (k < 0) | ||
break; | ||
|
||
n += k; | ||
|
||
if (user_only) | ||
break; | ||
} | ||
|
||
return n; | ||
} | ||
|
||
/** | ||
* early_platform_cleanup - clean up early platform code | ||
*/ | ||
static int __init early_platform_cleanup(void) | ||
{ | ||
struct platform_device *pd, *pd2; | ||
|
||
/* clean up the devres list used to chain devices */ | ||
list_for_each_entry_safe(pd, pd2, &early_platform_device_list, | ||
dev.devres_head) { | ||
list_del(&pd->dev.devres_head); | ||
memset(&pd->dev.devres_head, 0, sizeof(pd->dev.devres_head)); | ||
} | ||
|
||
return 0; | ||
} | ||
/* | ||
* This must happen once after all early devices are probed but before probing | ||
* real platform devices. | ||
*/ | ||
subsys_initcall(early_platform_cleanup); |
Oops, something went wrong.