diff options
author | Simon Glass <sjg@chromium.org> | 2015-03-25 21:22:37 +0300 |
---|---|---|
committer | Simon Glass <sjg@chromium.org> | 2015-04-18 20:11:26 +0300 |
commit | 019808f97c1d039c41a960e477554b956c3ae238 (patch) | |
tree | 5a3707ecb84c46d90cf72969f15b7762717e6f9d /drivers/usb | |
parent | f84c052a3e0975cac29ecaf9df2b824874e24237 (diff) | |
download | u-boot-019808f97c1d039c41a960e477554b956c3ae238.tar.xz |
dm: usb: sandbox: Add a uclass for USB device emulation
With sandbox we want to be able to emulate USB devices so that we can test
the USB stack. Add a uclass to support this. It implements the same
operations as a normal USB device driver, but in this case passes them on
to an emulation driver.
Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Marek Vasut <marex@denx.de>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/Kconfig | 2 | ||||
-rw-r--r-- | drivers/usb/emul/Kconfig | 8 | ||||
-rw-r--r-- | drivers/usb/emul/Makefile | 8 | ||||
-rw-r--r-- | drivers/usb/emul/usb-emul-uclass.c | 263 |
4 files changed, 281 insertions, 0 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index a4414efab7..637ef3d567 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -51,6 +51,8 @@ config DM_USB source "drivers/usb/host/Kconfig" +source "drivers/usb/emul/Kconfig" + config USB_STORAGE bool "USB Mass Storage support" ---help--- diff --git a/drivers/usb/emul/Kconfig b/drivers/usb/emul/Kconfig new file mode 100644 index 0000000000..ae1ab23a3d --- /dev/null +++ b/drivers/usb/emul/Kconfig @@ -0,0 +1,8 @@ +config USB_EMUL + bool "Support for USB device emulation" + depends on DM_USB && SANDBOX + help + Since sandbox does not have access to a real USB bus, it is possible + to use device emulators instead. This allows testing of the USB + stack on sandbox without needing a real device, or any host machine + USB resources. diff --git a/drivers/usb/emul/Makefile b/drivers/usb/emul/Makefile new file mode 100644 index 0000000000..f75bbd8eae --- /dev/null +++ b/drivers/usb/emul/Makefile @@ -0,0 +1,8 @@ +# +# (C) Copyright 2015 Google, Inc +# Written by Simon Glass <sjg@chromium.org> +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-$(CONFIG_USB_EMUL) += usb-emul-uclass.o diff --git a/drivers/usb/emul/usb-emul-uclass.c b/drivers/usb/emul/usb-emul-uclass.c new file mode 100644 index 0000000000..205f2c54df --- /dev/null +++ b/drivers/usb/emul/usb-emul-uclass.c @@ -0,0 +1,263 @@ +/* + * (C) Copyright 2015 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <usb.h> +#include <dm/root.h> +#include <dm/device-internal.h> + +DECLARE_GLOBAL_DATA_PTR; + +static int copy_to_unicode(char *buff, int length, const char *str) +{ + int ptr; + int i; + + if (length < 2) + return 0; + buff[1] = USB_DT_STRING; + for (ptr = 2, i = 0; ptr + 1 < length && *str; i++, ptr += 2) { + buff[ptr] = str[i]; + buff[ptr + 1] = 0; + } + buff[0] = ptr; + + return ptr; +} + +static int usb_emul_get_string(struct usb_string *strings, int index, + char *buff, int length) +{ + if (index == 0) { + char *desc = buff; + + desc[0] = 4; + desc[1] = USB_DT_STRING; + desc[2] = 0x09; + desc[3] = 0x14; + return 4; + } else if (strings) { + struct usb_string *ptr; + + for (ptr = strings; ptr->s; ptr++) { + if (ptr->id == index) + return copy_to_unicode(buff, length, ptr->s); + } + } + + return -EINVAL; +} + +static struct usb_generic_descriptor **find_descriptor( + struct usb_generic_descriptor **ptr, int type, int index) +{ + debug("%s: type=%x, index=%d\n", __func__, type, index); + for (; *ptr; ptr++) { + if ((*ptr)->bDescriptorType != type) + continue; + switch (type) { + case USB_DT_CONFIG: { + struct usb_config_descriptor *cdesc; + + cdesc = (struct usb_config_descriptor *)*ptr; + if (cdesc && cdesc->bConfigurationValue == index) + return ptr; + break; + } + default: + return ptr; + } + } + debug("%s: config ptr=%p\n", __func__, *ptr); + + return ptr; +} + +static int usb_emul_get_descriptor(struct usb_dev_platdata *plat, int value, + void *buffer, int length) +{ + struct usb_generic_descriptor **ptr; + int type = value >> 8; + int index = value & 0xff; + int upto, todo; + + debug("%s: type=%d, index=%d, plat=%p\n", __func__, type, index, plat); + if (type == USB_DT_STRING) { + return usb_emul_get_string(plat->strings, index, buffer, + length); + } + + ptr = find_descriptor((struct usb_generic_descriptor **)plat->desc_list, + type, index); + if (!ptr) { + debug("%s: Could not find descriptor type %d, index %d\n", + __func__, type, index); + return -ENOENT; + } + for (upto = 0; *ptr && upto < length; ptr++, upto += todo) { + todo = min(length - upto, (int)(*ptr)->bLength); + + memcpy(buffer + upto, *ptr, todo); + } + + return upto ? upto : length ? -EIO : 0; +} + +int usb_emul_find(struct udevice *bus, ulong pipe, struct udevice **emulp) +{ + int devnum = usb_pipedevice(pipe); + struct udevice *dev; + struct uclass *uc; + int ret; + + *emulp = NULL; + ret = uclass_get(UCLASS_USB_EMUL, &uc); + if (ret) + return ret; + uclass_foreach_dev(dev, uc) { + struct usb_dev_platdata *udev = dev_get_parent_platdata(dev); + + if (udev->devnum == devnum) { + debug("%s: Found emulator '%s', addr %d\n", __func__, + dev->name, udev->devnum); + *emulp = dev; + return 0; + } + } + + debug("%s: No emulator found, addr %d\n", __func__, devnum); + return -ENOENT; +} + +int usb_emul_control(struct udevice *emul, struct usb_device *udev, + unsigned long pipe, void *buffer, int length, + struct devrequest *setup) +{ + struct dm_usb_ops *ops = usb_get_emul_ops(emul); + struct usb_dev_platdata *plat; + int ret; + + /* We permit getting the descriptor before we are probed */ + plat = dev_get_parent_platdata(emul); + if (!ops->control) + return -ENOSYS; + debug("%s: dev=%s\n", __func__, emul->name); + if (pipe == usb_rcvctrlpipe(udev, 0)) { + switch (setup->request) { + case USB_REQ_GET_DESCRIPTOR: { + return usb_emul_get_descriptor(plat, setup->value, + buffer, length); + } + default: + ret = device_probe(emul); + if (ret) + return ret; + return ops->control(emul, udev, pipe, buffer, length, + setup); + } + } else if (pipe == usb_snddefctrl(udev)) { + switch (setup->request) { + case USB_REQ_SET_ADDRESS: + debug(" ** set address %s %d\n", emul->name, + setup->value); + plat->devnum = setup->value; + return 0; + default: + debug("requestsend =%x\n", setup->request); + break; + } + } else if (pipe == usb_sndctrlpipe(udev, 0)) { + switch (setup->request) { + case USB_REQ_SET_CONFIGURATION: + plat->configno = setup->value; + return 0; + default: + ret = device_probe(emul); + if (ret) + return ret; + return ops->control(emul, udev, pipe, buffer, length, + setup); + } + } + debug("pipe=%lx\n", pipe); + + return -EIO; +} + +int usb_emul_bulk(struct udevice *emul, struct usb_device *udev, + unsigned long pipe, void *buffer, int length) +{ + struct dm_usb_ops *ops = usb_get_emul_ops(emul); + int ret; + + /* We permit getting the descriptor before we are probed */ + if (!ops->bulk) + return -ENOSYS; + debug("%s: dev=%s\n", __func__, emul->name); + ret = device_probe(emul); + if (ret) + return ret; + return ops->bulk(emul, udev, pipe, buffer, length); +} + +int usb_emul_setup_device(struct udevice *dev, int maxpacketsize, + struct usb_string *strings, void **desc_list) +{ + struct usb_dev_platdata *plat = dev_get_parent_platdata(dev); + struct usb_generic_descriptor **ptr; + struct usb_config_descriptor *cdesc; + int upto; + + plat->strings = strings; + plat->desc_list = (struct usb_generic_descriptor **)desc_list; + + /* Fill in wTotalLength for each configuration descriptor */ + ptr = plat->desc_list; + for (cdesc = NULL, upto = 0; *ptr; upto += (*ptr)->bLength, ptr++) { + debug(" - upto=%d, type=%d\n", upto, (*ptr)->bDescriptorType); + if ((*ptr)->bDescriptorType == USB_DT_CONFIG) { + if (cdesc) { + cdesc->wTotalLength = upto; + debug("%s: config %d length %d\n", __func__, + cdesc->bConfigurationValue, + cdesc->bLength); + } + cdesc = (struct usb_config_descriptor *)*ptr; + upto = 0; + } + } + if (cdesc) { + cdesc->wTotalLength = upto; + debug("%s: config %d length %d\n", __func__, + cdesc->bConfigurationValue, cdesc->wTotalLength); + } + + return 0; +} + +int usb_emul_post_bind(struct udevice *dev) +{ + /* Scan the bus for devices */ + return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false); +} + +void usb_emul_reset(struct udevice *dev) +{ + struct usb_dev_platdata *plat = dev_get_parent_platdata(dev); + + plat->devnum = 0; + plat->configno = 0; +} + +UCLASS_DRIVER(usb_emul) = { + .id = UCLASS_USB_EMUL, + .name = "usb_emul", + .post_bind = usb_emul_post_bind, + .per_child_auto_alloc_size = sizeof(struct usb_device), + .per_child_platdata_auto_alloc_size = sizeof(struct usb_dev_platdata), +}; |