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@48380000/usb@48390000 usb_ether > unbind /ocp/omap_dwc3@48380000/usb@48390000 > > Signed-off-by: Jean-Jacques Hiblot <jjhib...@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 <jjhib...@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 > + > + > +@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 > + > +@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.si...@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 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de https://lists.denx.de/listinfo/u-boot