2014-11-11 00:16:47 +00:00
|
|
|
/*
|
|
|
|
* Device manager
|
|
|
|
*
|
|
|
|
* Copyright (c) 2014 Google, Inc
|
|
|
|
*
|
|
|
|
* (C) Copyright 2012
|
|
|
|
* Pavel Herrmann <morpheus.ibis@gmail.com>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <common.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <malloc.h>
|
|
|
|
#include <dm/device.h>
|
|
|
|
#include <dm/device-internal.h>
|
|
|
|
#include <dm/uclass.h>
|
|
|
|
#include <dm/uclass-internal.h>
|
|
|
|
#include <dm/util.h>
|
|
|
|
|
2015-11-09 06:47:58 +00:00
|
|
|
/**
|
|
|
|
* device_chld_unbind() - Unbind all device's children from the device
|
|
|
|
*
|
|
|
|
* On error, the function continues to unbind all children, and reports the
|
|
|
|
* first error.
|
|
|
|
*
|
|
|
|
* @dev: The device that is to be stripped of its children
|
|
|
|
* @return 0 on success, -ve on error
|
|
|
|
*/
|
|
|
|
static int device_chld_unbind(struct udevice *dev)
|
2014-11-11 00:16:47 +00:00
|
|
|
{
|
|
|
|
struct udevice *pos, *n;
|
|
|
|
int ret, saved_ret = 0;
|
|
|
|
|
|
|
|
assert(dev);
|
|
|
|
|
|
|
|
list_for_each_entry_safe(pos, n, &dev->child_head, sibling_node) {
|
|
|
|
ret = device_unbind(pos);
|
|
|
|
if (ret && !saved_ret)
|
|
|
|
saved_ret = ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return saved_ret;
|
|
|
|
}
|
|
|
|
|
2015-11-09 06:47:58 +00:00
|
|
|
/**
|
|
|
|
* device_chld_remove() - Stop all device's children
|
|
|
|
* @dev: The device whose children are to be removed
|
2017-03-20 11:51:48 +00:00
|
|
|
* @pre_os_remove: Flag, if this functions is called in the pre-OS stage
|
2015-11-09 06:47:58 +00:00
|
|
|
* @return 0 on success, -ve on error
|
|
|
|
*/
|
2017-03-20 11:51:48 +00:00
|
|
|
static int device_chld_remove(struct udevice *dev, uint flags)
|
2014-11-11 00:16:47 +00:00
|
|
|
{
|
|
|
|
struct udevice *pos, *n;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
assert(dev);
|
|
|
|
|
|
|
|
list_for_each_entry_safe(pos, n, &dev->child_head, sibling_node) {
|
2017-03-20 11:51:48 +00:00
|
|
|
ret = device_remove(pos, flags);
|
2014-11-11 00:16:47 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_unbind(struct udevice *dev)
|
|
|
|
{
|
2015-03-25 18:21:54 +00:00
|
|
|
const struct driver *drv;
|
2014-11-11 00:16:47 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!dev)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (dev->flags & DM_FLAG_ACTIVATED)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2015-07-25 12:52:34 +00:00
|
|
|
if (!(dev->flags & DM_FLAG_BOUND))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2014-11-11 00:16:47 +00:00
|
|
|
drv = dev->driver;
|
|
|
|
assert(drv);
|
|
|
|
|
|
|
|
if (drv->unbind) {
|
|
|
|
ret = drv->unbind(dev);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-11-09 06:47:58 +00:00
|
|
|
ret = device_chld_unbind(dev);
|
2014-11-11 00:16:47 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2015-01-25 15:27:00 +00:00
|
|
|
if (dev->flags & DM_FLAG_ALLOC_PDATA) {
|
|
|
|
free(dev->platdata);
|
|
|
|
dev->platdata = NULL;
|
|
|
|
}
|
2015-04-15 11:07:18 +00:00
|
|
|
if (dev->flags & DM_FLAG_ALLOC_UCLASS_PDATA) {
|
|
|
|
free(dev->uclass_platdata);
|
|
|
|
dev->uclass_platdata = NULL;
|
|
|
|
}
|
2015-01-25 15:27:01 +00:00
|
|
|
if (dev->flags & DM_FLAG_ALLOC_PARENT_PDATA) {
|
|
|
|
free(dev->parent_platdata);
|
|
|
|
dev->parent_platdata = NULL;
|
|
|
|
}
|
2014-11-11 00:16:47 +00:00
|
|
|
ret = uclass_unbind_device(dev);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (dev->parent)
|
|
|
|
list_del(&dev->sibling_node);
|
devres: introduce Devres (Managed Device Resource) framework
In U-Boot's driver model, memory is basically allocated and freed
in the core framework. So, low level drivers generally only have
to specify the size of needed memory with .priv_auto_alloc_size,
.platdata_auto_alloc_size, etc. Nevertheless, some drivers still
need to allocate/free memory on their own in case they cannot
statically know the necessary memory size. So, I believe it is
reasonable enough to port Devres into U-boot.
Devres, which originates in Linux, manages device resources for each
device and automatically releases them on driver detach. With devres,
device resources are guaranteed to be freed whether initialization
fails half-way or the device gets detached.
The basic idea is totally the same to that of Linux, but I tweaked
it a bit so that it fits in U-Boot's driver model.
In U-Boot, drivers are activated in two steps: binding and probing.
Binding puts a driver and a device together. It is just data
manipulation on the system memory, so nothing has happened on the
hardware device at this moment. When the device is really used, it
is probed. Probing initializes the real hardware device to make it
really ready for use.
So, the resources acquired during the probing process must be freed
when the device is removed. Likewise, what has been allocated in
binding should be released when the device is unbound. The struct
devres has a member "probe" to remember when the resource was
allocated.
CONFIG_DEBUG_DEVRES is also supported for easier debugging.
If enabled, debug messages are printed each time a resource is
allocated/freed.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Acked-by: Simon Glass <sjg@chromium.org>
2015-07-25 12:52:35 +00:00
|
|
|
|
|
|
|
devres_release_all(dev);
|
|
|
|
|
2016-07-04 17:58:15 +00:00
|
|
|
if (dev->flags & DM_FLAG_NAME_ALLOCED)
|
2016-05-01 19:52:23 +00:00
|
|
|
free((char *)dev->name);
|
2014-11-11 00:16:47 +00:00
|
|
|
free(dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* device_free() - Free memory buffers allocated by a device
|
|
|
|
* @dev: Device that is to be started
|
|
|
|
*/
|
|
|
|
void device_free(struct udevice *dev)
|
|
|
|
{
|
|
|
|
int size;
|
|
|
|
|
|
|
|
if (dev->driver->priv_auto_alloc_size) {
|
|
|
|
free(dev->priv);
|
|
|
|
dev->priv = NULL;
|
|
|
|
}
|
|
|
|
size = dev->uclass->uc_drv->per_device_auto_alloc_size;
|
|
|
|
if (size) {
|
|
|
|
free(dev->uclass_priv);
|
|
|
|
dev->uclass_priv = NULL;
|
|
|
|
}
|
|
|
|
if (dev->parent) {
|
|
|
|
size = dev->parent->driver->per_child_auto_alloc_size;
|
2015-01-25 15:27:06 +00:00
|
|
|
if (!size) {
|
|
|
|
size = dev->parent->uclass->uc_drv->
|
|
|
|
per_child_auto_alloc_size;
|
|
|
|
}
|
2014-11-11 00:16:47 +00:00
|
|
|
if (size) {
|
|
|
|
free(dev->parent_priv);
|
|
|
|
dev->parent_priv = NULL;
|
|
|
|
}
|
|
|
|
}
|
devres: introduce Devres (Managed Device Resource) framework
In U-Boot's driver model, memory is basically allocated and freed
in the core framework. So, low level drivers generally only have
to specify the size of needed memory with .priv_auto_alloc_size,
.platdata_auto_alloc_size, etc. Nevertheless, some drivers still
need to allocate/free memory on their own in case they cannot
statically know the necessary memory size. So, I believe it is
reasonable enough to port Devres into U-boot.
Devres, which originates in Linux, manages device resources for each
device and automatically releases them on driver detach. With devres,
device resources are guaranteed to be freed whether initialization
fails half-way or the device gets detached.
The basic idea is totally the same to that of Linux, but I tweaked
it a bit so that it fits in U-Boot's driver model.
In U-Boot, drivers are activated in two steps: binding and probing.
Binding puts a driver and a device together. It is just data
manipulation on the system memory, so nothing has happened on the
hardware device at this moment. When the device is really used, it
is probed. Probing initializes the real hardware device to make it
really ready for use.
So, the resources acquired during the probing process must be freed
when the device is removed. Likewise, what has been allocated in
binding should be released when the device is unbound. The struct
devres has a member "probe" to remember when the resource was
allocated.
CONFIG_DEBUG_DEVRES is also supported for easier debugging.
If enabled, debug messages are printed each time a resource is
allocated/freed.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Acked-by: Simon Glass <sjg@chromium.org>
2015-07-25 12:52:35 +00:00
|
|
|
|
|
|
|
devres_release_probe(dev);
|
2014-11-11 00:16:47 +00:00
|
|
|
}
|
|
|
|
|
2017-04-24 07:48:02 +00:00
|
|
|
static bool flags_remove(uint flags, uint drv_flags)
|
|
|
|
{
|
|
|
|
if ((flags & DM_REMOVE_NORMAL) ||
|
|
|
|
(flags & (drv_flags & (DM_FLAG_ACTIVE_DMA | DM_FLAG_OS_PREPARE))))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-20 11:51:48 +00:00
|
|
|
int device_remove(struct udevice *dev, uint flags)
|
2014-11-11 00:16:47 +00:00
|
|
|
{
|
2015-03-25 18:21:54 +00:00
|
|
|
const struct driver *drv;
|
2014-11-11 00:16:47 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!dev)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (!(dev->flags & DM_FLAG_ACTIVATED))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
drv = dev->driver;
|
|
|
|
assert(drv);
|
|
|
|
|
|
|
|
ret = uclass_pre_remove_device(dev);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2017-03-20 11:51:48 +00:00
|
|
|
ret = device_chld_remove(dev, flags);
|
2014-11-11 00:16:47 +00:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2017-03-27 08:58:53 +00:00
|
|
|
/*
|
|
|
|
* Remove the device if called with the "normal" remove flag set,
|
|
|
|
* or if the remove flag matches any of the drivers remove flags
|
|
|
|
*/
|
2017-04-24 07:48:02 +00:00
|
|
|
if (drv->remove && flags_remove(flags, drv->flags)) {
|
2014-11-11 00:16:47 +00:00
|
|
|
ret = drv->remove(dev);
|
|
|
|
if (ret)
|
|
|
|
goto err_remove;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dev->parent && dev->parent->driver->child_post_remove) {
|
|
|
|
ret = dev->parent->driver->child_post_remove(dev);
|
|
|
|
if (ret) {
|
|
|
|
dm_warn("%s: Device '%s' failed child_post_remove()",
|
|
|
|
__func__, dev->name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-24 07:48:02 +00:00
|
|
|
if (flags_remove(flags, drv->flags)) {
|
2017-03-27 08:58:53 +00:00
|
|
|
device_free(dev);
|
2014-11-11 00:16:47 +00:00
|
|
|
|
2017-03-27 08:58:53 +00:00
|
|
|
dev->seq = -1;
|
|
|
|
dev->flags &= ~DM_FLAG_ACTIVATED;
|
|
|
|
}
|
2014-11-11 00:16:47 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
err_remove:
|
|
|
|
/* We can't put the children back */
|
|
|
|
dm_warn("%s: Device '%s' failed to remove, but children are gone\n",
|
|
|
|
__func__, dev->name);
|
|
|
|
err:
|
|
|
|
ret = uclass_post_probe_device(dev);
|
|
|
|
if (ret) {
|
|
|
|
dm_warn("%s: Device '%s' failed to post_probe on error path\n",
|
|
|
|
__func__, dev->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|