On 08/10/2017 08:29 PM, Rob Clark wrote: > Helpers to construct device-paths from devices, partitions, files, and > for parsing and manipulating device-paths. > > For non-legacy devices, this will use u-boot's device-model to construct > device-paths which include bus hierarchy to construct device-paths. For > legacy devices we still fake it, but slightly more convincingly. > > Signed-off-by: Rob Clark <robdcl...@gmail.com> > --- > include/efi_api.h | 10 + > include/efi_loader.h | 20 ++ > lib/efi_loader/Makefile | 2 +- > lib/efi_loader/efi_device_path.c | 489 > +++++++++++++++++++++++++++++++++++++++ > 4 files changed, 520 insertions(+), 1 deletion(-) > create mode 100644 lib/efi_loader/efi_device_path.c > > diff --git a/include/efi_api.h b/include/efi_api.h > index b761cf4822..4e27c82129 100644 > --- a/include/efi_api.h > +++ b/include/efi_api.h > @@ -314,6 +314,7 @@ struct efi_device_path_acpi_path { > #define DEVICE_PATH_TYPE_MESSAGING_DEVICE 0x03 > # define DEVICE_PATH_SUB_TYPE_MSG_USB 0x05 > # define DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR 0x0b > +# define DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS 0x0f > # define DEVICE_PATH_SUB_TYPE_MSG_SD 0x1a > # define DEVICE_PATH_SUB_TYPE_MSG_MMC 0x1d > > @@ -329,6 +330,15 @@ struct efi_device_path_mac_addr { > u8 if_type; > } __packed; > > +struct efi_device_path_usb_class { > + struct efi_device_path dp; > + u16 vendor_id; > + u16 product_id; > + u8 device_class; > + u8 device_subclass; > + u8 device_protocol; > +} __packed; > + > struct efi_device_path_sd_mmc_path { > struct efi_device_path dp; > u8 slot_number; > diff --git a/include/efi_loader.h b/include/efi_loader.h > index 037cc7c543..bcca6e49ea 100644 > --- a/include/efi_loader.h > +++ b/include/efi_loader.h > @@ -197,6 +197,26 @@ extern void *efi_bounce_buffer; > #define EFI_LOADER_BOUNCE_BUFFER_SIZE (64 * 1024 * 1024) > #endif > > + > +struct efi_device_path *efi_dp_next(struct efi_device_path *dp); > +int efi_dp_match(struct efi_device_path *a, struct efi_device_path *b); > +struct efi_object *efi_dp_find_obj(struct efi_device_path *dp); > +unsigned efi_dp_size(struct efi_device_path *dp); > +struct efi_device_path *efi_dp_dup(struct efi_device_path *dp); > + > +struct efi_device_path *efi_dp_from_dev(struct udevice *dev); > +struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part); > +struct efi_device_path *efi_dp_from_file(struct blk_desc *desc, int part, > + const char *path); > +struct efi_device_path *efi_dp_from_eth(void); > +void efi_dp_split_file_path(struct efi_device_path *full_path, > + struct efi_device_path **device_path, > + struct efi_device_path **file_path); > + > +#define EFI_DP_TYPE(_dp, _type, _subtype) \ > + (((_dp)->type == DEVICE_PATH_TYPE_##_type) && \ > + ((_dp)->sub_type == DEVICE_PATH_SUB_TYPE_##_subtype)) > + > /* Convert strings from normal C strings to uEFI strings */ > static inline void ascii2unicode(u16 *unicode, const char *ascii) > { > diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile > index 30bf343a36..f35e5ce8a8 100644 > --- a/lib/efi_loader/Makefile > +++ b/lib/efi_loader/Makefile > @@ -15,7 +15,7 @@ always := $(efiprogs-y) > > obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o > obj-y += efi_image_loader.o efi_boottime.o efi_runtime.o efi_console.o > -obj-y += efi_memory.o efi_device_path_to_text.o > +obj-y += efi_memory.o efi_device_path_to_text.o efi_device_path.o > obj-$(CONFIG_LCD) += efi_gop.o > obj-$(CONFIG_DM_VIDEO) += efi_gop.o > obj-$(CONFIG_PARTITIONS) += efi_disk.o > diff --git a/lib/efi_loader/efi_device_path.c > b/lib/efi_loader/efi_device_path.c > new file mode 100644 > index 0000000000..e8a6bbff82 > --- /dev/null > +++ b/lib/efi_loader/efi_device_path.c > @@ -0,0 +1,489 @@ > +/* > + * EFI device path from u-boot device-model mapping > + * > + * (C) Copyright 2017 Rob Clark > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#include <common.h> > +#include <blk.h> > +#include <dm.h> > +#include <usb.h> > +#include <mmc.h> > +#include <efi_loader.h> > +#include <inttypes.h> > +#include <part.h> > +#include <malloc.h> > + > +/* template END node: */ > +static const struct efi_device_path END = { > + .type = DEVICE_PATH_TYPE_END, > + .sub_type = DEVICE_PATH_SUB_TYPE_END, > + .length = sizeof(END), > +}; > + > +#define U_BOOT_GUID \ > + EFI_GUID(0xe61d73b9, 0xa384, 0x4acc, \ > + 0xae, 0xab, 0x82, 0xe8, 0x28, 0xf3, 0x62, 0x8b) > + > +/* template ROOT node, a fictional ACPI PNP device: */ > +static const struct efi_device_path_vendor ROOT = { > + .dp = { > + .type = DEVICE_PATH_TYPE_HARDWARE_DEVICE, > + .sub_type = DEVICE_PATH_SUB_TYPE_VENDOR, > + .length = sizeof(ROOT), > + }, > + .guid = U_BOOT_GUID, > +}; > + > + > +/* > + * Iterate to next block in device-path, terminating (returning NULL) > + * at /End* node. > + */ > +struct efi_device_path *efi_dp_next(struct efi_device_path *dp) > +{ > + if (dp == NULL) > + return NULL; > + dp = ((void *)dp) + dp->length; > + if (dp->type == DEVICE_PATH_TYPE_END) > + return NULL; > + return dp; > +} > + > +/* > + * Compare two device-paths, stopping when the shorter of the two hits > + * an End* node. This is useful to, for example, compare a device-path > + * representing a device with one representing a file on the device, or > + * a device with a parent device. > + */ > +int efi_dp_match(struct efi_device_path *a, struct efi_device_path *b) > +{ > + while (1) { > + int ret; > + > + ret = memcmp(&a->length, &b->length, sizeof(a->length)); > + if (ret) > + return ret; > + > + ret = memcmp(a, b, a->length); > + if (ret) > + return ret; > + > + a = efi_dp_next(a); > + b = efi_dp_next(b); > + > + if (!a || !b) > + return 0; > + } > +} > + > + > +/* > + * See UEFI spec (section 3.1.2, about short-form device-paths.. > + * tl;dr: we can have a device-path that starts with a USB WWID > + * or USB Class node, and a few other cases which don't encode > + * the full device path with bus hierarchy: > + * > + * - MESSAGING:USB_WWID > + * - MESSAGING:USB_CLASS > + * - MEDIA:FILE_PATH > + * - MEDIA:HARD_DRIVE > + * - MESSAGING:URI > + */ > +static struct efi_device_path *shorten_path(struct efi_device_path *dp) > +{ > + while (dp) { > + /* > + * TODO: Add MESSAGING:USB_WWID and MESSAGING:URI.. > + * in practice fallback.efi just uses MEDIA:HARD_DRIVE > + * so not sure when we would see these other cases. > + */ > + if (EFI_DP_TYPE(dp, MESSAGING_DEVICE, MSG_USB_CLASS) || > + EFI_DP_TYPE(dp, MEDIA_DEVICE, HARD_DRIVE_PATH) || > + EFI_DP_TYPE(dp, MEDIA_DEVICE, FILE_PATH)) > + return dp; > + > + dp = efi_dp_next(dp); > + } > + > + return dp; > +} > + > +static struct efi_object *find_obj(struct efi_device_path *dp, bool > short_path) > +{ > + struct efi_object *efiobj; > + > + list_for_each_entry(efiobj, &efi_obj_list, link) { > + int i; > + > + for (i = 0; i < ARRAY_SIZE(efiobj->protocols); i++) { > + struct efi_handler *handler = &efiobj->protocols[i]; > + struct efi_device_path *obj_dp; > + > + if (!handler->guid) > + break; > + > + if (guidcmp(handler->guid, &efi_guid_device_path)) > + continue; > + > + obj_dp = handler->protocol_interface; > + > + do { > + if (efi_dp_match(dp, obj_dp) == 0) > + return efiobj; > + > + obj_dp = shorten_path(efi_dp_next(obj_dp)); > + } while (short_path && obj_dp); > + } > + } > + > + return NULL; > +} > + > + > +/* Find an efiobj from device-path */ > +struct efi_object *efi_dp_find_obj(struct efi_device_path *dp) > +{ > + struct efi_object *efiobj; > + > + efiobj = find_obj(dp, false); > + > + if (!efiobj) > + efiobj = find_obj(dp, true); > + > + return efiobj; > +} > + > +/* return size not including End node: */ > +unsigned efi_dp_size(struct efi_device_path *dp) > +{ > + unsigned sz = 0; > + > + while (dp) { > + sz += dp->length; > + dp = efi_dp_next(dp); > + } > + > + return sz; > +} > + > +struct efi_device_path *efi_dp_dup(struct efi_device_path *dp) > +{ > + struct efi_device_path *ndp; > + unsigned sz = efi_dp_size(dp) + sizeof(struct efi_device_path); > + > + ndp = malloc(sz); > + memcpy(ndp, dp, sz); > + > + return ndp; > +} > + > +#ifdef CONFIG_DM > +/* size of device-path not including END node for device and all parents > + * up to the root device. > + */ > +static unsigned dp_size(struct udevice *dev) > +{ > + if (!dev || !dev->driver) > + return sizeof(ROOT); > + > + switch (dev->driver->id) { > + case UCLASS_ROOT: > + case UCLASS_SIMPLE_BUS: > + /* stop traversing parents at this point: */ > + return sizeof(ROOT); > + case UCLASS_MMC: > + return dp_size(dev->parent) + sizeof(struct > efi_device_path_sd_mmc_path); > + case UCLASS_MASS_STORAGE: > + case UCLASS_USB_HUB: > + return dp_size(dev->parent) + sizeof(struct > efi_device_path_usb_class);
It seems you forgot to run scripts/checkpatch.pl for your patches: Line over 80 characters. Please, use line break. > + default: > + /* just skip over unknown classes: */ > + return dp_size(dev->parent); > + } > +} > + > +static void *dp_fill(void *buf, struct udevice *dev) > +{ > + if (!dev || !dev->driver) > + return buf; > + > + switch (dev->driver->id) { > + case UCLASS_ROOT: > + case UCLASS_SIMPLE_BUS: { > + /* stop traversing parents at this point: */ > + struct efi_device_path_vendor *vdp = buf; > + *vdp = ROOT; > + return &vdp[1]; > + } > +#if defined(CONFIG_DM_MMC) && defined (CONFIG_MMC) scripts/checkpatch.pl: WARNING: space prohibited between function name and open parenthesis '(' > + case UCLASS_MMC: { > + struct efi_device_path_sd_mmc_path *sddp = > + dp_fill(buf, dev->parent); > + struct mmc *mmc = mmc_get_mmc_dev(dev); > + struct blk_desc *desc = mmc_get_blk_desc(mmc); > + > + sddp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; > + sddp->dp.sub_type = (desc->if_type == IF_TYPE_MMC) ? > + DEVICE_PATH_SUB_TYPE_MSG_MMC : > + DEVICE_PATH_SUB_TYPE_MSG_SD; > + sddp->dp.length = sizeof(*sddp); > + sddp->slot_number = 0; // XXX ??? > + > + return &sddp[1]; > + } > +#endif > + case UCLASS_MASS_STORAGE: > + case UCLASS_USB_HUB: { > + struct efi_device_path_usb_class *udp = > + dp_fill(buf, dev->parent); > + struct usb_device *udev = dev_get_parent_priv(dev); > + struct usb_device_descriptor *desc = &udev->descriptor; > + > + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; > + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS; > + udp->dp.length = sizeof(*udp); > + udp->vendor_id = desc->idVendor; > + udp->product_id = desc->idProduct; > + udp->device_class = desc->bDeviceClass; > + udp->device_subclass = desc->bDeviceSubClass; > + udp->device_protocol = desc->bDeviceProtocol; > + > + return &udp[1]; > + } > + default: > + debug("unhandled device class: %s (%u)\n", > + dev->name, dev->driver->id); scripts/checkpatch.pl: Alignment should match open parenthesis > + return dp_fill(buf, dev->parent); > + } > +} > + > +/* Construct a device-path from a device: */ > +struct efi_device_path *efi_dp_from_dev(struct udevice *dev) > +{ > + void *buf, *start; > + > + start = buf = calloc(1, dp_size(dev) + sizeof(END)); > + buf = dp_fill(buf, dev); > + *((struct efi_device_path *)buf) = END; > + > + return start; > +} > +#endif > + > +static unsigned dp_part_size(struct blk_desc *desc, int part) > +{ > + unsigned dpsize; > + > +#ifdef CONFIG_BLK > + dpsize = dp_size(desc->bdev->parent); > +#else > + dpsize = sizeof(ROOT) + sizeof(struct efi_device_path_usb); > +#endif > + > + if (part == 0) /* the actual disk, not a partition */ > + return dpsize; > + > + if (desc->part_type == PART_TYPE_ISO) { > + dpsize += sizeof(struct efi_device_path_cdrom_path); > + } else { > + dpsize += sizeof(struct efi_device_path_hard_drive_path); > + } Please, have a look at all warnings produced by scripts/checkpatch.pl. Here you get: WARNING: braces {} are not necessary for any arm of this statement #464: FILE: lib/efi_loader/efi_device_path.c:289: + if (desc->part_type == PART_TYPE_ISO) { [...] + } else { [...] Best regards Heinrich > + > + return dpsize; > +} > + > +static void *dp_part_fill(void *buf, struct blk_desc *desc, int part) > +{ > + disk_partition_t info; > + > +#ifdef CONFIG_BLK > + buf = dp_fill(buf, desc->bdev->parent); > +#else > + /* > + * We *could* make a more accurate path, by looking at if_type > + * and handling all the different cases like we do for non- > + * legacy (ie CONFIG_BLK=y) case. But most important thing > + * is just to have a unique device-path for if_type+devnum. > + * So map things to a fictional USB device: > + */ > + struct efi_device_path_usb *udp; > + > + memcpy(buf, &ROOT, sizeof(ROOT)); > + buf += sizeof(ROOT); > + > + udp = buf; > + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; > + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB; > + udp->dp.length = sizeof(*udp); > + udp->parent_port_number = desc->if_type; > + udp->usb_interface = desc->devnum; > + buf = &udp[1]; > +#endif > + > + if (part == 0) /* the actual disk, not a partition */ > + return buf; > + > + part_get_info(desc, part, &info); > + > + if (desc->part_type == PART_TYPE_ISO) { > + struct efi_device_path_cdrom_path *cddp = buf; > + > + cddp->boot_entry = part - 1; > + cddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; > + cddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_CDROM_PATH; > + cddp->dp.length = sizeof (*cddp); > + cddp->partition_start = info.start; > + cddp->partition_end = info.size; > + > + buf = &cddp[1]; > + } else { > + struct efi_device_path_hard_drive_path *hddp = buf; > + > + hddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; > + hddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH; > + hddp->dp.length = sizeof (*hddp); > + hddp->partition_number = part - 1; > + hddp->partition_start = info.start; > + hddp->partition_end = info.size; > + if (desc->part_type == PART_TYPE_EFI) > + hddp->partmap_type = 2; > + else > + hddp->partmap_type = 1; > + hddp->signature_type = desc->sig_type; > + if (hddp->signature_type != 0) > + memcpy(hddp->partition_signature, &desc->guid_sig, > + sizeof(hddp->partition_signature)); > + > + buf = &hddp[1]; > + } > + > + return buf; > +} > + > + > +/* Construct a device-path from a partition on a blk device: */ > +struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part) > +{ > + void *buf, *start; > + > + start = buf = calloc(1, dp_part_size(desc, part) + sizeof(END)); > + > + buf = dp_part_fill(buf, desc, part); > + > + *((struct efi_device_path *)buf) = END; > + > + return start; > +} > + > +/* convert path to an UEFI style path (ie. DOS style backslashes and utf16) > */ > +static void path_to_uefi(u16 *uefi, const char *path) > +{ > + while (*path) { > + char c = *(path++); > + if (c == '/') > + c = '\\'; > + *(uefi++) = c; > + } > + *uefi = '\0'; > +} > + > +/* > + * If desc is NULL, this creates a path with only the file component, > + * otherwise it creates a full path with both device and file components > + */ > +struct efi_device_path *efi_dp_from_file(struct blk_desc *desc, int part, > + const char *path) > +{ > + struct efi_device_path_file_path *fp; > + void *buf, *start; > + unsigned dpsize = 0, fpsize; > + > + if (desc) > + dpsize = dp_part_size(desc, part); > + > + // TODO efi_device_path_file_path should be variable length: > + fpsize = sizeof(struct efi_device_path) + 2 * (strlen(path) + 1); > + dpsize += fpsize; > + > + start = buf = calloc(1, dpsize + sizeof(END)); > + > + if (desc) > + buf = dp_part_fill(buf, desc, part); > + > + /* add file-path: */ > + fp = buf; > + fp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; > + fp->dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH; > + fp->dp.length = fpsize; > + path_to_uefi(fp->str, path); > + buf += fpsize; > + > + *((struct efi_device_path *)buf) = END; > + > + return start; > +} > + > +#ifdef CONFIG_NET > +struct efi_device_path *efi_dp_from_eth(void) > +{ > + struct efi_device_path_mac_addr *ndp; > + void *buf, *start; > + unsigned dpsize = 0; > + > + assert(eth_get_dev()); > + > +#ifdef CONFIG_DM_ETH > + dpsize += dp_size(eth_get_dev()); > +#else > + dpsize += sizeof(ROOT); > +#endif > + dpsize += sizeof(*ndp); > + > + start = buf = calloc(1, dpsize + sizeof(END)); > + > +#ifdef CONFIG_DM_ETH > + buf = dp_fill(buf, eth_get_dev()); > +#else > + memcpy(buf, &ROOT, sizeof(ROOT)); > + buf += sizeof(ROOT); > +#endif > + > + ndp = buf; > + ndp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; > + ndp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR; > + ndp->dp.length = sizeof(*ndp); > + memcpy(ndp->mac.addr, eth_get_ethaddr(), ARP_HLEN); > + buf = &ndp[1]; > + > + *((struct efi_device_path *)buf) = END; > + > + return start; > +} > +#endif > + > +/* > + * Helper to split a full device path (containing both device and file > + * parts) into it's constituent parts. > + */ > +void efi_dp_split_file_path(struct efi_device_path *full_path, > + struct efi_device_path **device_path, > + struct efi_device_path **file_path) > +{ > + struct efi_device_path *p, *dp, *fp; > + > + dp = efi_dp_dup(full_path); > + p = dp; > + while (!EFI_DP_TYPE(p, MEDIA_DEVICE, FILE_PATH)) > + p = efi_dp_next(p); > + fp = efi_dp_dup(p); > + > + p->type = DEVICE_PATH_TYPE_END; > + p->sub_type = DEVICE_PATH_SUB_TYPE_END; > + p->length = sizeof(*p); > + > + *device_path = dp; > + *file_path = fp; > +} > _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de https://lists.denx.de/listinfo/u-boot