From: Simon Glass Date: Wed, 25 Mar 2015 18:22:37 +0000 (-0600) Subject: dm: usb: sandbox: Add a uclass for USB device emulation X-Git-Url: http://git.dujemihanovic.xyz/?a=commitdiff_plain;h=019808f97c1d039c41a960e477554b956c3ae238;p=u-boot.git 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 Reviewed-by: Marek Vasut --- diff --git a/Makefile b/Makefile index 0f7d58388e..dc25f70ce0 100644 --- a/Makefile +++ b/Makefile @@ -651,6 +651,7 @@ libs-$(CONFIG_FMAN_ENET) += drivers/net/fm/ libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/ libs-y += drivers/serial/ libs-y += drivers/usb/dwc3/ +libs-y += drivers/usb/emul/ libs-y += drivers/usb/eth/ libs-y += drivers/usb/gadget/ libs-y += drivers/usb/gadget/udc/ 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 +# +# 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 + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +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), +}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 6fe3669968..3d62dbbb2a 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -21,6 +21,7 @@ enum uclass_id { UCLASS_SPI_EMUL, /* sandbox SPI device emulator */ UCLASS_I2C_EMUL, /* sandbox I2C device emulator */ UCLASS_PCI_EMUL, /* sandbox PCI device emulator */ + UCLASS_USB_EMUL, /* sandbox USB bus device emulator */ UCLASS_SIMPLE_BUS, /* U-Boot uclasses start here */ diff --git a/include/usb.h b/include/usb.h index 6db2140dd5..1984e8f590 100644 --- a/include/usb.h +++ b/include/usb.h @@ -179,7 +179,8 @@ enum usb_init_type { defined(CONFIG_USB_BLACKFIN) || defined(CONFIG_USB_AM35X) || \ defined(CONFIG_USB_MUSB_DSPS) || defined(CONFIG_USB_MUSB_AM35X) || \ defined(CONFIG_USB_MUSB_OMAP2PLUS) || defined(CONFIG_USB_MUSB_SUNXI) || \ - defined(CONFIG_USB_XHCI) || defined(CONFIG_USB_DWC2) + defined(CONFIG_USB_XHCI) || defined(CONFIG_USB_DWC2) || \ + defined(CONFIG_USB_EMUL) int usb_lowlevel_init(int index, enum usb_init_type init, void **controller); int usb_lowlevel_stop(int index); @@ -848,4 +849,65 @@ int usb_new_device(struct usb_device *dev); int usb_alloc_device(struct usb_device *dev); +/** + * usb_emul_setup_device() - Set up a new USB device emulation + * + * This is normally called when a new emulation device is bound. It tells + * the USB emulation uclass about the features of the emulator. + * + * @dev: Emulation device + * @maxpacketsize: Maximum packet size (e.g. PACKET_SIZE_64) + * @strings: List of USB string descriptors, terminated by a NULL + * entry + * @desc_list: List of points or USB descriptors, terminated by NULL. + * The first entry must be struct usb_device_descriptor, + * and others follow on after that. + * @return 0 if OK, -ve on error + */ +int usb_emul_setup_device(struct udevice *dev, int maxpacketsize, + struct usb_string *strings, void **desc_list); + +/** + * usb_emul_control() - Send a control packet to an emulator + * + * @emul: Emulator device + * @udev: USB device (which the emulator is causing to appear) + * See struct dm_usb_ops for details on other parameters + * @return 0 if OK, -ve on error + */ +int usb_emul_control(struct udevice *emul, struct usb_device *udev, + unsigned long pipe, void *buffer, int length, + struct devrequest *setup); + +/** + * usb_emul_bulk() - Send a bulk packet to an emulator + * + * @emul: Emulator device + * @udev: USB device (which the emulator is causing to appear) + * See struct dm_usb_ops for details on other parameters + * @return 0 if OK, -ve on error + */ +int usb_emul_bulk(struct udevice *emul, struct usb_device *udev, + unsigned long pipe, void *buffer, int length); + +/** + * usb_emul_find() - Find an emulator for a particular device + * + * Check @pipe to find a device number on bus @bus and return it. + * + * @bus: USB bus (controller) + * @pipe: Describes pipe being used, and includes the device number + * @emulp: Returns pointer to emulator, or NULL if not found + * @return 0 if found, -ve on error + */ +int usb_emul_find(struct udevice *bus, ulong pipe, struct udevice **emulp); + +/** + * usb_emul_reset() - Reset all emulators ready for use + * + * Clear out any address information in the emulators and make then ready for + * a new USB scan + */ +void usb_emul_reset(struct udevice *dev); + #endif /*_USB_H_ */