[U-Boot] [PATCH v3 7/7] cmd: Add bind/unbind commands to bind a device to a driver from the command line

Michal Simek michal.simek at xilinx.com
Wed Jun 27 14:13:30 UTC 2018


On 22.6.2018 14:25, Jean-Jacques Hiblot wrote:
> In some cases it can be useful to be able to bind a device to a driver from
> the command line.
> The obvious example is for versatile devices such as USB gadget.
> Another use case is when the devices are not yet ready at startup and
> require some setup before the drivers are bound (ex: FPGA which bitsream is
> fetched from a mass storage or ethernet)
> 
> usage example:
> 
> bind usb_dev_generic 0 usb_ether
> unbind usb_dev_generic 0 usb_ether
> or
> unbind eth 1
> 
> bind /ocp/omap_dwc3 at 48380000/usb at 48390000 usb_ether
> unbind /ocp/omap_dwc3 at 48380000/usb at 48390000
> 
> Signed-off-by: Jean-Jacques Hiblot <jjhiblot at ti.com>
> 
> ---
> 
> Changes in v3:
> - factorize code based on comments from ML
> - remove the devices before unbinding them
> - use device_find_global_by_ofnode() to get a device by its node.
> - Added tests
> 
> Changes in v2:
> - Make the bind/unbind command generic, not specific to usb device.
> - Update the API to be able to bind/unbind based on DTS node path
> - Add a Kconfig option to select the bind/unbind commands
> 
>  arch/sandbox/dts/test.dts  |  11 ++
>  cmd/Kconfig                |   9 ++
>  cmd/Makefile               |   1 +
>  cmd/bind.c                 | 255 +++++++++++++++++++++++++++++++++++++++++++++
>  configs/sandbox_defconfig  |   1 +
>  test/py/tests/test_bind.py | 178 +++++++++++++++++++++++++++++++
>  6 files changed, 455 insertions(+)
>  create mode 100644 cmd/bind.c
>  create mode 100644 test/py/tests/test_bind.py
> 
> diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
> index 5752bf5..8c789b3 100644
> --- a/arch/sandbox/dts/test.dts
> +++ b/arch/sandbox/dts/test.dts
> @@ -58,6 +58,17 @@
>  		reg = <2 1>;
>  	};
>  
> +	bind-test {
> +		bind-test-child1 {
> +			compatible = "sandbox,phy";
> +			#phy-cells = <1>;
> +		};
> +
> +		bind-test-child2 {
> +			compatible = "simple-bus";
> +		};
> +	};
> +
>  	b-test {
>  		reg = <3 1>;
>  		compatible = "denx,u-boot-fdt-test";
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index 1eb55e5..d6bbfba 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -607,6 +607,15 @@ config CMD_ADC
>  	  Shows ADC device info and permit printing one-shot analog converted
>  	  data from a named Analog to Digital Converter.
>  
> +config CMD_BIND
> +	bool "bind/unbind - Bind or unbind a device to/from a driver"
> +	depends on DM
> +	help
> +	  Bind or unbind a device to/from a driver from the command line.
> +	  This is useful in situations where a device may be handled by several
> +	  drivers. For example, this can be used to bind a UDC to the usb ether
> +	  gadget driver from the command line.
> +
>  config CMD_CLK
>  	bool "clk - Show clock frequencies"
>  	help
> diff --git a/cmd/Makefile b/cmd/Makefile
> index e0088df..b12aca3 100644
> --- a/cmd/Makefile
> +++ b/cmd/Makefile
> @@ -19,6 +19,7 @@ obj-$(CONFIG_SOURCE) += source.o
>  obj-$(CONFIG_CMD_SOURCE) += source.o
>  obj-$(CONFIG_CMD_BDI) += bdinfo.o
>  obj-$(CONFIG_CMD_BEDBUG) += bedbug.o
> +obj-$(CONFIG_CMD_BIND) += bind.o
>  obj-$(CONFIG_CMD_BINOP) += binop.o
>  obj-$(CONFIG_CMD_BLOCK_CACHE) += blkcache.o
>  obj-$(CONFIG_CMD_BMP) += bmp.o
> diff --git a/cmd/bind.c b/cmd/bind.c
> new file mode 100644
> index 0000000..a213d26
> --- /dev/null
> +++ b/cmd/bind.c
> @@ -0,0 +1,255 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (c) 2018 JJ Hiblot <jjhiblot at ti.com>
> + */
> +
> +#include <common.h>
> +#include <dm.h>
> +#include <dm/device-internal.h>
> +#include <dm/lists.h>
> +#include <dm/uclass-internal.h>
> +
> +static int bind_by_class_index(const char *uclass, int index,
> +			       const char *drv_name)
> +{
> +	static enum uclass_id uclass_id;
> +	struct udevice *dev;
> +	struct udevice *parent;
> +	int ret;
> +	struct driver *drv;
> +
> +	drv = lists_driver_lookup_name(drv_name);
> +	if (!drv) {
> +		printf("Cannot find driver '%s'\n", drv_name);
> +		return -ENOENT;
> +	}
> +
> +	uclass_id = uclass_get_by_name(uclass);
> +	if (uclass_id == UCLASS_INVALID) {
> +		printf("%s is not a valid uclass\n", uclass);
> +		return -EINVAL;
> +	}
> +
> +	ret = uclass_find_device(uclass_id, index, &parent);
> +	if (!parent || ret) {
> +		printf("Cannot find device %d of class %s\n", index, uclass);
> +		return ret;
> +	}
> +
> +	ret = device_bind_with_driver_data(parent, drv, drv->name, 0,
> +					   ofnode_null(), &dev);
> +	if (!dev || ret) {
> +		printf("Unable to bind. err:%d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int find_dev(const char *uclass, int index, struct udevice **devp)
> +{
> +	static enum uclass_id uclass_id;
> +	int rc;
> +
> +	uclass_id = uclass_get_by_name(uclass);
> +	if (uclass_id == UCLASS_INVALID) {
> +		printf("%s is not a valid uclass\n", uclass);
> +		return -EINVAL;
> +	}
> +
> +	rc = uclass_find_device(uclass_id, index, devp);
> +	if (!*devp || rc) {
> +		printf("Cannot find device %d of class %s\n", index, uclass);
> +		return rc;
> +	}
> +
> +	return 0;
> +}
> +
> +static int unbind_by_class_index(const char *uclass, int index)
> +{
> +	int ret;
> +	struct udevice *dev;
> +
> +	ret = find_dev(uclass, index, &dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = device_remove(dev, DM_REMOVE_NORMAL);
> +	if (ret) {
> +		printf("Unable to remove. err:%d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = device_unbind(dev);
> +	if (ret) {
> +		printf("Unable to unbind. err:%d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int unbind_child_by_class_index(const char *uclass, int index,
> +				       const char *drv_name)
> +{
> +	struct udevice *parent;
> +	int ret;
> +	struct driver *drv;
> +
> +	drv = lists_driver_lookup_name(drv_name);
> +	if (!drv) {
> +		printf("Cannot find driver '%s'\n", drv_name);
> +		return -ENOENT;
> +	}
> +
> +	ret = find_dev(uclass, index, &parent);
> +	if (ret)
> +		return ret;
> +
> +	ret = device_chld_remove(parent, drv, DM_REMOVE_NORMAL);
> +	if (ret)
> +		printf("Unable to remove all. err:%d\n", ret);
> +
> +	ret = device_chld_unbind(parent, drv);
> +	if (ret)
> +		printf("Unable to unbind all. err:%d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int bind_by_node_path(const char *path, const char *drv_name)
> +{
> +	struct udevice *dev;
> +	struct udevice *parent = NULL;
> +	int ret;
> +	ofnode ofnode;
> +	struct driver *drv;
> +
> +	drv = lists_driver_lookup_name(drv_name);
> +	if (!drv) {
> +		printf("%s is not a valid driver name\n", drv_name);
> +		return -ENOENT;
> +	}
> +
> +	ofnode = ofnode_path(path);
> +	if (!ofnode_valid(ofnode)) {
> +		printf("%s is not a valid node path\n", path);
> +		return -EINVAL;
> +	}
> +
> +	while (ofnode_valid(ofnode)) {
> +		if (!device_find_global_by_ofnode(ofnode, &parent))
> +			break;
> +		ofnode = ofnode_get_parent(ofnode);
> +	}
> +
> +	if (!parent) {
> +		printf("Cannot find a parent device for node path %s\n", path);
> +		return -ENODEV;
> +	}
> +
> +	ofnode = ofnode_path(path);
> +	ret = device_bind_with_driver_data(parent, drv, ofnode_get_name(ofnode),
> +					   0, ofnode, &dev);
> +	if (!dev || ret) {
> +		printf("Unable to bind. err:%d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int unbind_by_node_path(const char *path)
> +{
> +	struct udevice *dev;
> +	int ret;
> +	ofnode ofnode;
> +
> +	ofnode = ofnode_path(path);
> +	if (!ofnode_valid(ofnode)) {
> +		printf("%s is not a valid node path\n", path);
> +		return -EINVAL;
> +	}
> +
> +	ret = device_find_global_by_ofnode(ofnode, &dev);
> +
> +	if (!dev || ret) {
> +		printf("Cannot find a device with path %s\n", path);
> +		return -ENODEV;
> +	}
> +
> +	ret = device_remove(dev, DM_REMOVE_NORMAL);
> +	if (ret) {
> +		printf("Unable to remove. err:%d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = device_unbind(dev);
> +	if (ret) {
> +		printf("Unable to unbind. err:%d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int do_bind_unbind(cmd_tbl_t *cmdtp, int flag, int argc,
> +			  char * const argv[])
> +{
> +	int ret;
> +	bool bind;
> +	bool by_node;
> +
> +	if (argc < 2)
> +		return CMD_RET_USAGE;
> +
> +	bind = (argv[0][0] == 'b');
> +	by_node = (argv[1][0] == '/');
> +
> +	if (by_node && bind) {
> +		if (argc != 3)
> +			return CMD_RET_USAGE;
> +		ret = bind_by_node_path(argv[1], argv[2]);
> +	} else if (by_node && !bind) {
> +		if (argc != 2)
> +			return CMD_RET_USAGE;
> +		ret = unbind_by_node_path(argv[1]);
> +	} else if (!by_node && bind) {
> +		int index = (argc > 2) ? simple_strtoul(argv[2], NULL, 10) : 0;
> +
> +		if (argc != 4)
> +			return CMD_RET_USAGE;
> +		ret = bind_by_class_index(argv[1], index, argv[3]);
> +	} else if (!by_node && !bind) {
> +		int index = (argc > 2) ? simple_strtoul(argv[2], NULL, 10) : 0;
> +
> +		if (argc == 3)
> +			ret = unbind_by_class_index(argv[1], index);
> +		else if (argc == 4)
> +			ret = unbind_child_by_class_index(argv[1], index,
> +							  argv[3]);
> +		else
> +			return CMD_RET_USAGE;
> +	}
> +
> +	if (ret)
> +		return CMD_RET_FAILURE;
> +	else
> +		return CMD_RET_SUCCESS;
> +}
> +
> +U_BOOT_CMD(
> +	bind,	4,	0,	do_bind_unbind,
> +	"Bind a device to a driver",
> +	"<node path> <driver>\n"
> +	"bind <class> <index> <driver>\n"
> +);
> +
> +U_BOOT_CMD(
> +	unbind,	4,	0,	do_bind_unbind,
> +	"Unbind a device from a driver",
> +	"<node path>\n"
> +	"unbind <class> <index>\n"
> +	"unbind <class> <index> <driver>\n"
> +);
> diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
> index 2fc84a1..cb4fdf4 100644
> --- a/configs/sandbox_defconfig
> +++ b/configs/sandbox_defconfig
> @@ -33,6 +33,7 @@ CONFIG_CMD_MD5SUM=y
>  CONFIG_CMD_MEMINFO=y
>  CONFIG_CMD_MEMTEST=y
>  CONFIG_CMD_MX_CYCLIC=y
> +CONFIG_CMD_BIND=y
>  CONFIG_CMD_DEMO=y
>  CONFIG_CMD_GPIO=y
>  CONFIG_CMD_GPT=y
> diff --git a/test/py/tests/test_bind.py b/test/py/tests/test_bind.py
> new file mode 100644
> index 0000000..f21b705
> --- /dev/null
> +++ b/test/py/tests/test_bind.py
> @@ -0,0 +1,178 @@
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
> +
> +import os.path
> +import pytest
> +import re
> +
> +def in_tree(response, name, uclass, drv, depth, last_child):
> +	lines = [x.strip() for x in response.splitlines()]
> +	leaf = ' ' * 4 * depth;
> +	if not last_child:
> +		leaf = leaf + '\|'
> +	else:
> +		leaf = leaf + '`'
> +	leaf = leaf + '-- ' + name
> +	line = ' *{:10.10}  [0-9]*  \[ [ +] \]   {:10.10}  {}$'.format(uclass, drv,leaf)
> +	prog = re.compile(line)
> +	for l in lines:
> +		if prog.match(l):
> +			return True
> +	return False
> +
> +
> + at pytest.mark.buildconfigspec('cmd_bind')
> +def test_bind_unbind_with_node(u_boot_console):
> +
> +	#bind /bind-test. Device should come up as well as its children
> +	response = u_boot_console.run_command("bind  /bind-test generic_simple_bus")
> +	assert response == ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert in_tree(tree, "bind-test", "simple_bus", "generic_simple", 0, True)
> +	assert in_tree(tree, "bind-test-child1", "phy", "phy_sandbox", 1, False)
> +	assert in_tree(tree, "bind-test-child2", "simple_bus", "generic_simple", 1, True)
> +
> +	#Unbind child #1. No error expected and all devices should be there except for bind-test-child1
> +	response = u_boot_console.run_command("unbind  /bind-test/bind-test-child1")
> +	assert response == ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert in_tree(tree, "bind-test", "simple_bus", "generic_simple", 0, True)
> +	assert "bind-test-child1" not in tree
> +	assert in_tree(tree, "bind-test-child2", "simple_bus", "generic_simple", 1, True)
> +
> +	#bind child #1. No error expected and all devices should be there
> +	response = u_boot_console.run_command("bind  /bind-test/bind-test-child1 phy_sandbox")
> +	assert response == ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert in_tree(tree, "bind-test", "simple_bus", "generic_simple", 0, True)
> +	assert in_tree(tree, "bind-test-child1", "phy", "phy_sandbox", 1, True)
> +	assert in_tree(tree, "bind-test-child2", "simple_bus", "generic_simple", 1, False)
> +
> +	#Unbind child #2. No error expected and all devices should be there except for bind-test-child2
> +	response = u_boot_console.run_command("unbind  /bind-test/bind-test-child2")
> +	assert response == ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert in_tree(tree, "bind-test", "simple_bus", "generic_simple", 0, True)
> +	assert in_tree(tree, "bind-test-child1", "phy", "phy_sandbox", 1, True)
> +	assert "bind-test-child2" not in tree
> +
> +
> +	#Bind child #2. No error expected and all devices should be there
> +	response = u_boot_console.run_command("bind /bind-test/bind-test-child2 generic_simple_bus")
> +	assert response == ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert in_tree(tree, "bind-test", "simple_bus", "generic_simple", 0, True)
> +	assert in_tree(tree, "bind-test-child1", "phy", "phy_sandbox", 1, False)
> +	assert in_tree(tree, "bind-test-child2", "simple_bus", "generic_simple", 1, True)
> +
> +	#Unbind parent. No error expected. All devices should be removed and unbound
> +	response = u_boot_console.run_command("unbind  /bind-test")
> +	assert response == ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert "bind-test" not in tree
> +	assert "bind-test-child1" not in tree
> +	assert "bind-test-child2" not in tree
> +
> +	#try binding invalid node with valid driver
> +	response = u_boot_console.run_command("bind  /not-a-valid-node generic_simple_bus")
> +	assert response != ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert "not-a-valid-node" not in tree
> +
> +	#try binding valid node with invalid driver
> +	response = u_boot_console.run_command("bind  /bind-test not_a_driver")
> +	assert response != ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert "bind-test" not in tree
> +
> +	#bind /bind-test. Device should come up as well as its children
> +	response = u_boot_console.run_command("bind  /bind-test generic_simple_bus")
> +	assert response == ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert in_tree(tree, "bind-test", "simple_bus", "generic_simple", 0, True)
> +	assert in_tree(tree, "bind-test-child1", "phy", "phy_sandbox", 1, False)
> +	assert in_tree(tree, "bind-test-child2", "simple_bus", "generic_simple", 1, True)
> +
> +	response = u_boot_console.run_command("unbind  /bind-test")
> +	assert response == ''
> +
> +def get_next_line(tree, name):
> +	treelines = [x.strip() for x in tree.splitlines() if x.strip()]
> +	child_line = ""
> +	for idx, line in enumerate(treelines):
> +		if ("-- " + name) in line:
> +			try:
> +				child_line = treelines[idx+1]
> +			except:
> +				pass
> +			break
> +	return child_line
> +
> + at pytest.mark.buildconfigspec('cmd_bind')
> +def test_bind_unbind_with_uclass(u_boot_console):
> +	#bind /bind-test
> +	response = u_boot_console.run_command("bind  /bind-test generic_simple_bus")
> +	assert response == ''
> +
> +	#make sure bind-test-child2 is there and get its uclass/index pair
> +	tree = u_boot_console.run_command("dm tree")
> +	child2_line = [x.strip() for x in tree.splitlines() if "-- bind-test-child2" in x]
> +	assert len(child2_line) == 1
> +
> +	child2_uclass = child2_line[0].split()[0]
> +	child2_index = int(child2_line[0].split()[1])
> +
> +	#bind generic_simple_bus as a child of bind-test-child2
> +	response = u_boot_console.run_command("bind  {} {} generic_simple_bus".format(child2_uclass, child2_index, "generic_simple_bus"))
> +
> +	#check that the child is there and its uclass/index pair is right
> +	tree = u_boot_console.run_command("dm tree")
> +
> +	child_of_child2_line = get_next_line(tree, "bind-test-child2")
> +	assert child_of_child2_line
> +	child_of_child2_index = int(child_of_child2_line.split()[1])
> +	assert in_tree(tree, "generic_simple_bus", "simple_bus", "generic_simple_bus", 2, True)
> +	assert child_of_child2_index == child2_index + 1
> +
> +	#unbind the child and check it has been removed
> +	response = u_boot_console.run_command("unbind  simple_bus {}".format(child_of_child2_index))
> +	assert response == ''
> +	tree = u_boot_console.run_command("dm tree")
> +	assert in_tree(tree, "bind-test-child2", "simple_bus", "generic_simple", 1, True)
> +	assert not in_tree(tree, "generic_simple_bus", "simple_bus", "generic_simple_bus", 2, True)
> +	child_of_child2_line = get_next_line(tree, "bind-test-child2")
> +	assert child_of_child2_line == ""
> +
> +	#bind generic_simple_bus as a child of bind-test-child2
> +	response = u_boot_console.run_command("bind  {} {} generic_simple_bus".format(child2_uclass, child2_index, "generic_simple_bus"))
> +
> +	#check that the child is there and its uclass/index pair is right
> +	tree = u_boot_console.run_command("dm tree")
> +	treelines = [x.strip() for x in tree.splitlines() if x.strip()]
> +
> +	child_of_child2_line = get_next_line(tree, "bind-test-child2")
> +	assert child_of_child2_line
> +	child_of_child2_index = int(child_of_child2_line.split()[1])
> +	assert in_tree(tree, "generic_simple_bus", "simple_bus", "generic_simple_bus", 2, True)
> +	assert child_of_child2_index == child2_index + 1
> +
> +	#unbind the child and check it has been removed
> +	response = u_boot_console.run_command("unbind  {} {} generic_simple_bus".format(child2_uclass, child2_index, "generic_simple_bus"))
> +	assert response == ''
> +
> +	tree = u_boot_console.run_command("dm tree")
> +	assert in_tree(tree, "bind-test-child2", "simple_bus", "generic_simple", 1, True)
> +
> +	child_of_child2_line = get_next_line(tree, "bind-test-child2")
> +	assert child_of_child2_line == ""
> +
> +	#unbind the child again and check it doesn't change the tree
> +	tree_old = u_boot_console.run_command("dm tree")
> +	response = u_boot_console.run_command("unbind  {} {} generic_simple_bus".format(child2_uclass, child2_index, "generic_simple_bus"))
> +	tree_new = u_boot_console.run_command("dm tree")
> +
> +	assert response == ''
> +	assert tree_old == tree_new
> +
> +	response = u_boot_console.run_command("unbind  /bind-test")
> +	assert response == ''
> 


I have tested bind/unbind with dwc3 on zynqmp for ethernet gadget and it
is working fine for me.
I have also tried to use bind/unbind for gpio zynqmp driver and it is
also working fine.

It means
Tested-by: Michal Simek <michal.simek at xilinx.com>

I would prefer if these commands are called as dm bind and dm unbind
instead of simply bind/unbind which should also fit to dm command
description "dm - Driver model low level access".


Thanks,
Michal




More information about the U-Boot mailing list