efi_loader: eliminate inline function ascii2unicode()
[oweals/u-boot.git] / lib / efi_loader / efi_device_path.c
index f00a0ce64567ee73b02d0428ef80786924d36f49..ea39f13b735cc52828c06fd2bfaf8300c3714a0d 100644 (file)
@@ -1,9 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0+
 /*
  * EFI device path from u-boot device-model mapping
  *
  * (C) Copyright 2017 Rob Clark
- *
- * SPDX-License-Identifier:    GPL-2.0+
  */
 
 #include <common.h>
@@ -12,8 +11,8 @@
 #include <usb.h>
 #include <mmc.h>
 #include <efi_loader.h>
-#include <inttypes.h>
 #include <part.h>
+#include <asm-generic/unaligned.h>
 
 /* template END node: */
 static const struct efi_device_path END = {
@@ -22,10 +21,6 @@ static const struct efi_device_path END = {
        .length   = sizeof(END),
 };
 
-#define U_BOOT_GUID \
-       EFI_GUID(0xe61d73b9, 0xa384, 0x4acc, \
-                0xae, 0xab, 0x82, 0xe8, 0x28, 0xf3, 0x62, 0x8b)
-
 /* template ROOT node: */
 static const struct efi_device_path_vendor ROOT = {
        .dp = {
@@ -64,6 +59,7 @@ static void *dp_alloc(size_t sz)
                return NULL;
        }
 
+       memset(buf, 0, sz);
        return buf;
 }
 
@@ -85,7 +81,7 @@ struct efi_device_path *efi_dp_next(const struct efi_device_path *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
+ * 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.
  */
@@ -111,18 +107,18 @@ int efi_dp_match(const struct efi_device_path *a,
        }
 }
 
-
 /*
- * 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:
+ * We can have device paths that start with a USB WWID or a 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
+ *
+ * See UEFI spec (section 3.1.2, about short-form device-paths)
  */
 static struct efi_device_path *shorten_path(struct efi_device_path *dp)
 {
@@ -147,14 +143,14 @@ static struct efi_object *find_obj(struct efi_device_path *dp, bool short_path,
                                   struct efi_device_path **rem)
 {
        struct efi_object *efiobj;
-       unsigned int dp_size = efi_dp_size(dp);
+       efi_uintn_t dp_size = efi_dp_instance_size(dp);
 
        list_for_each_entry(efiobj, &efi_obj_list, link) {
                struct efi_handler *handler;
                struct efi_device_path *obj_dp;
                efi_status_t ret;
 
-               ret = efi_search_protocol(efiobj->handle,
+               ret = efi_search_protocol(efiobj,
                                          &efi_guid_device_path, &handler);
                if (ret != EFI_SUCCESS)
                        continue;
@@ -168,11 +164,12 @@ static struct efi_object *find_obj(struct efi_device_path *dp, bool short_path,
                                         * the caller.
                                         */
                                        *rem = ((void *)dp) +
-                                               efi_dp_size(obj_dp);
+                                               efi_dp_instance_size(obj_dp);
                                        return efiobj;
                                } else {
                                        /* Only return on exact matches */
-                                       if (efi_dp_size(obj_dp) == dp_size)
+                                       if (efi_dp_instance_size(obj_dp) ==
+                                           dp_size)
                                                return efiobj;
                                }
                        }
@@ -184,7 +181,6 @@ static struct efi_object *find_obj(struct efi_device_path *dp, bool short_path,
        return NULL;
 }
 
-
 /*
  * Find an efiobj from device-path, if 'rem' is not NULL, returns the
  * remaining part of the device path after the matched object.
@@ -208,11 +204,33 @@ struct efi_object *efi_dp_find_obj(struct efi_device_path *dp,
        return efiobj;
 }
 
-/* return size not including End node: */
-unsigned efi_dp_size(const struct efi_device_path *dp)
+/*
+ * Determine the last device path node that is not the end node.
+ *
+ * @dp         device path
+ * @return     last node before the end node if it exists
+ *             otherwise NULL
+ */
+const struct efi_device_path *efi_dp_last_node(const struct efi_device_path *dp)
+{
+       struct efi_device_path *ret;
+
+       if (!dp || dp->type == DEVICE_PATH_TYPE_END)
+               return NULL;
+       while (dp) {
+               ret = (struct efi_device_path *)dp;
+               dp = efi_dp_next(dp);
+       }
+       return ret;
+}
+
+/* get size of the first device path instance excluding end node */
+efi_uintn_t efi_dp_instance_size(const struct efi_device_path *dp)
 {
-       unsigned sz = 0;
+       efi_uintn_t sz = 0;
 
+       if (!dp || dp->type == DEVICE_PATH_TYPE_END)
+               return 0;
        while (dp) {
                sz += dp->length;
                dp = efi_dp_next(dp);
@@ -221,10 +239,25 @@ unsigned efi_dp_size(const struct efi_device_path *dp)
        return sz;
 }
 
+/* get size of multi-instance device path excluding end node */
+efi_uintn_t efi_dp_size(const struct efi_device_path *dp)
+{
+       const struct efi_device_path *p = dp;
+
+       if (!p)
+               return 0;
+       while (p->type != DEVICE_PATH_TYPE_END ||
+              p->sub_type != DEVICE_PATH_SUB_TYPE_END)
+               p = (void *)p + p->length;
+
+       return (void *)p - (void *)dp;
+}
+
+/* copy multi-instance device path */
 struct efi_device_path *efi_dp_dup(const struct efi_device_path *dp)
 {
        struct efi_device_path *ndp;
-       unsigned sz = efi_dp_size(dp) + sizeof(END);
+       size_t sz = efi_dp_size(dp) + sizeof(END);
 
        if (!dp)
                return NULL;
@@ -242,7 +275,10 @@ struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1,
 {
        struct efi_device_path *ret;
 
-       if (!dp1) {
+       if (!dp1 && !dp2) {
+               /* return an end node */
+               ret = efi_dp_dup(&END);
+       } else if (!dp1) {
                ret = efi_dp_dup(dp2);
        } else if (!dp2) {
                ret = efi_dp_dup(dp1);
@@ -254,8 +290,8 @@ struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1,
                if (!p)
                        return NULL;
                memcpy(p, dp1, sz1);
-               memcpy(p + sz1, dp2, sz2);
-               memcpy(p + sz1 + sz2, &END, sizeof(END));
+               /* the end node of the second device path has to be retained */
+               memcpy(p + sz1, dp2, sz2 + sizeof(END));
                ret = p;
        }
 
@@ -272,7 +308,7 @@ struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp,
        } else if (!node) {
                ret = efi_dp_dup(dp);
        } else if (!dp) {
-               unsigned sz = node->length;
+               size_t sz = node->length;
                void *p = dp_alloc(sz + sizeof(END));
                if (!p)
                        return NULL;
@@ -281,7 +317,7 @@ struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp,
                ret = p;
        } else {
                /* both dp and node are non-null */
-               unsigned sz = efi_dp_size(dp);
+               size_t sz = efi_dp_size(dp);
                void *p = dp_alloc(sz + node->length + sizeof(END));
                if (!p)
                        return NULL;
@@ -294,6 +330,87 @@ struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp,
        return ret;
 }
 
+struct efi_device_path *efi_dp_create_device_node(const u8 type,
+                                                 const u8 sub_type,
+                                                 const u16 length)
+{
+       struct efi_device_path *ret;
+
+       if (length < sizeof(struct efi_device_path))
+               return NULL;
+
+       ret = dp_alloc(length);
+       if (!ret)
+               return ret;
+       ret->type = type;
+       ret->sub_type = sub_type;
+       ret->length = length;
+       return ret;
+}
+
+struct efi_device_path *efi_dp_append_instance(
+               const struct efi_device_path *dp,
+               const struct efi_device_path *dpi)
+{
+       size_t sz, szi;
+       struct efi_device_path *p, *ret;
+
+       if (!dpi)
+               return NULL;
+       if (!dp)
+               return efi_dp_dup(dpi);
+       sz = efi_dp_size(dp);
+       szi = efi_dp_instance_size(dpi);
+       p = dp_alloc(sz + szi + 2 * sizeof(END));
+       if (!p)
+               return NULL;
+       ret = p;
+       memcpy(p, dp, sz + sizeof(END));
+       p = (void *)p + sz;
+       p->sub_type = DEVICE_PATH_SUB_TYPE_INSTANCE_END;
+       p = (void *)p + sizeof(END);
+       memcpy(p, dpi, szi);
+       p = (void *)p + szi;
+       memcpy(p, &END, sizeof(END));
+       return ret;
+}
+
+struct efi_device_path *efi_dp_get_next_instance(struct efi_device_path **dp,
+                                                efi_uintn_t *size)
+{
+       size_t sz;
+       struct efi_device_path *p;
+
+       if (size)
+               *size = 0;
+       if (!dp || !*dp)
+               return NULL;
+       sz = efi_dp_instance_size(*dp);
+       p = dp_alloc(sz + sizeof(END));
+       if (!p)
+               return NULL;
+       memcpy(p, *dp, sz + sizeof(END));
+       *dp = (void *)*dp + sz;
+       if ((*dp)->sub_type == DEVICE_PATH_SUB_TYPE_INSTANCE_END)
+               *dp = (void *)*dp + sizeof(END);
+       else
+               *dp = NULL;
+       if (size)
+               *size = sz + sizeof(END);
+       return p;
+}
+
+bool efi_dp_is_multi_instance(const struct efi_device_path *dp)
+{
+       const struct efi_device_path *p = dp;
+
+       if (!p)
+               return false;
+       while (p->type != DEVICE_PATH_TYPE_END)
+               p = (void *)p + p->length;
+       return p->sub_type == DEVICE_PATH_SUB_TYPE_INSTANCE_END;
+}
+
 #ifdef CONFIG_DM
 /* size of device-path not including END node for device and all parents
  * up to the root device.
@@ -308,6 +425,9 @@ static unsigned dp_size(struct udevice *dev)
        case UCLASS_SIMPLE_BUS:
                /* stop traversing parents at this point: */
                return sizeof(ROOT);
+       case UCLASS_ETH:
+               return dp_size(dev->parent) +
+                       sizeof(struct efi_device_path_mac_addr);
 #ifdef CONFIG_BLK
        case UCLASS_BLK:
                switch (dev->parent->uclass->uc_drv->id) {
@@ -320,14 +440,21 @@ static unsigned dp_size(struct udevice *dev)
                case UCLASS_SCSI:
                        return dp_size(dev->parent) +
                                sizeof(struct efi_device_path_scsi);
+#endif
+#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC)
+               case UCLASS_MMC:
+                       return dp_size(dev->parent) +
+                               sizeof(struct efi_device_path_sd_mmc_path);
 #endif
                default:
                        return dp_size(dev->parent);
                }
 #endif
+#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC)
        case UCLASS_MMC:
                return dp_size(dev->parent) +
                        sizeof(struct efi_device_path_sd_mmc_path);
+#endif
        case UCLASS_MASS_STORAGE:
        case UCLASS_USB_HUB:
                return dp_size(dev->parent) +
@@ -358,6 +485,23 @@ static void *dp_fill(void *buf, struct udevice *dev)
                *vdp = ROOT;
                return &vdp[1];
        }
+#ifdef CONFIG_DM_ETH
+       case UCLASS_ETH: {
+               struct efi_device_path_mac_addr *dp =
+                       dp_fill(buf, dev->parent);
+               struct eth_pdata *pdata = dev->platdata;
+
+               dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE;
+               dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR;
+               dp->dp.length = sizeof(*dp);
+               memset(&dp->mac, 0, sizeof(dp->mac));
+               /* We only support IPv4 */
+               memcpy(&dp->mac, &pdata->enetaddr, ARP_HLEN);
+               /* Ethernet */
+               dp->if_type = 1;
+               return &dp[1];
+       }
+#endif
 #ifdef CONFIG_BLK
        case UCLASS_BLK:
                switch (dev->parent->uclass->uc_drv->id) {
@@ -391,10 +535,26 @@ static void *dp_fill(void *buf, struct udevice *dev)
                        dp->target_id = desc->target;
                        return &dp[1];
                        }
+#endif
+#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC)
+               case UCLASS_MMC: {
+                       struct efi_device_path_sd_mmc_path *sddp =
+                               dp_fill(buf, dev->parent);
+                       struct blk_desc *desc = dev_get_uclass_platdata(dev);
+
+                       sddp->dp.type     = DEVICE_PATH_TYPE_MESSAGING_DEVICE;
+                       sddp->dp.sub_type = is_sd(desc) ?
+                               DEVICE_PATH_SUB_TYPE_MSG_SD :
+                               DEVICE_PATH_SUB_TYPE_MSG_MMC;
+                       sddp->dp.length   = sizeof(*sddp);
+                       sddp->slot_number = dev->seq;
+                       return &sddp[1];
+                       }
 #endif
                default:
-                       printf("unhandled parent class: %s (%u)\n",
-                              dev->name, dev->driver->id);
+                       debug("%s(%u) %s: unhandled parent class: %s (%u)\n",
+                             __FILE__, __LINE__, __func__,
+                             dev->name, dev->parent->uclass->uc_drv->id);
                        return dp_fill(buf, dev->parent);
                }
 #endif
@@ -434,7 +594,8 @@ static void *dp_fill(void *buf, struct udevice *dev)
                return &udp[1];
        }
        default:
-               debug("unhandled device class: %s (%u)\n",
+               debug("%s(%u) %s: unhandled device class: %s (%u)\n",
+                     __FILE__, __LINE__, __func__,
                      dev->name, dev->driver->id);
                return dp_fill(buf, dev->parent);
        }
@@ -486,7 +647,7 @@ static unsigned dp_part_size(struct blk_desc *desc, int part)
 /*
  * Create a device node for a block device partition.
  *
- * @buf                buffer to which the device path is wirtten
+ * @buf                buffer to which the device path is written
  * @desc       block device descriptor
  * @part       partition number, 0 identifies a block device
  */
@@ -504,7 +665,7 @@ static void *dp_part_node(void *buf, struct blk_desc *desc, int part)
                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;
+               cddp->partition_size = info.size;
 
                buf = &cddp[1];
        } else {
@@ -551,7 +712,7 @@ static void *dp_part_node(void *buf, struct blk_desc *desc, int part)
 /*
  * Create a device path for a block device or one of its partitions.
  *
- * @buf                buffer to which the device path is wirtten
+ * @buf                buffer to which the device path is written
  * @desc       block device descriptor
  * @part       partition number, 0 identifies a block device
  */
@@ -570,7 +731,7 @@ static void *dp_part_fill(void *buf, struct blk_desc *desc, int part)
        /*
         * 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
+        * legacy (i.e. 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 fictitious USB device.
         */
@@ -594,7 +755,7 @@ static void *dp_part_fill(void *buf, struct blk_desc *desc, int part)
        return dp_part_node(buf, desc, part);
 }
 
-/* Construct a device-path from a partition on a blk device: */
+/* Construct a device-path from a partition on a block device: */
 struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part)
 {
        void *buf, *start;
@@ -613,7 +774,7 @@ struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part)
 /*
  * Create a device node for a block device partition.
  *
- * @buf                buffer to which the device path is wirtten
+ * @buf                buffer to which the device path is written
  * @desc       block device descriptor
  * @part       partition number, 0 identifies a block device
  */
@@ -633,16 +794,36 @@ struct efi_device_path *efi_dp_part_node(struct blk_desc *desc, int part)
        return buf;
 }
 
-/* convert path to an UEFI style path (ie. DOS style backslashes and utf16) */
-static void path_to_uefi(u16 *uefi, const char *path)
+/**
+ * path_to_uefi() - convert UTF-8 path to an UEFI style path
+ *
+ * Convert UTF-8 path to a UEFI style path (i.e. with backslashes as path
+ * separators and UTF-16).
+ *
+ * @src:       source buffer
+ * @uefi:      target buffer, possibly unaligned
+ */
+static void path_to_uefi(void *uefi, const char *src)
 {
-       while (*path) {
-               char c = *(path++);
-               if (c == '/')
-                       c = '\\';
-               *(uefi++) = c;
+       u16 *pos = uefi;
+
+       /*
+        * efi_set_bootdev() calls this routine indirectly before the UEFI
+        * subsystem is initialized. So we cannot assume unaligned access to be
+        * enabled.
+        */
+       allow_unaligned();
+
+       while (*src) {
+               s32 code = utf8_get(&src);
+
+               if (code < 0)
+                       code = '?';
+               else if (code == '/')
+                       code = '\\';
+               utf16_put(code, &pos);
        }
-       *uefi = '\0';
+       *pos = 0;
 }
 
 /*
@@ -659,7 +840,8 @@ struct efi_device_path *efi_dp_from_file(struct blk_desc *desc, int part,
        if (desc)
                dpsize = dp_part_size(desc, part);
 
-       fpsize = sizeof(struct efi_device_path) + 2 * (strlen(path) + 1);
+       fpsize = sizeof(struct efi_device_path) +
+                2 * (utf8_utf16_strlen(path) + 1);
        dpsize += fpsize;
 
        start = buf = dp_alloc(dpsize + sizeof(END));
@@ -685,7 +867,9 @@ struct efi_device_path *efi_dp_from_file(struct blk_desc *desc, int part,
 #ifdef CONFIG_NET
 struct efi_device_path *efi_dp_from_eth(void)
 {
+#ifndef CONFIG_DM_ETH
        struct efi_device_path_mac_addr *ndp;
+#endif
        void *buf, *start;
        unsigned dpsize = 0;
 
@@ -695,8 +879,8 @@ struct efi_device_path *efi_dp_from_eth(void)
        dpsize += dp_size(eth_get_dev());
 #else
        dpsize += sizeof(ROOT);
-#endif
        dpsize += sizeof(*ndp);
+#endif
 
        start = buf = dp_alloc(dpsize + sizeof(END));
        if (!buf)
@@ -707,14 +891,15 @@ struct efi_device_path *efi_dp_from_eth(void)
 #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);
+       ndp->if_type = 1; /* Ethernet */
        memcpy(ndp->mac.addr, eth_get_ethaddr(), ARP_HLEN);
        buf = &ndp[1];
+#endif
 
        *((struct efi_device_path *)buf) = END;
 
@@ -748,15 +933,23 @@ struct efi_device_path *efi_dp_from_mem(uint32_t memory_type,
        return start;
 }
 
-/*
- * Helper to split a full device path (containing both device and file
- * parts) into it's constituent parts.
+/**
+ * efi_dp_split_file_path() - split of relative file path from device path
+ *
+ * Given a device path indicating a file on a device, separate the device
+ * path in two: the device path of the actual device and the file path
+ * relative to this device.
+ *
+ * @full_path:         device path including device and file path
+ * @device_path:       path of the device
+ * @file_path:         relative path of the file or NULL if there is none
+ * Return:             status code
  */
 efi_status_t 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;
+       struct efi_device_path *p, *dp, *fp = NULL;
 
        *device_path = NULL;
        *file_path = NULL;
@@ -767,7 +960,7 @@ efi_status_t efi_dp_split_file_path(struct efi_device_path *full_path,
        while (!EFI_DP_TYPE(p, MEDIA_DEVICE, FILE_PATH)) {
                p = efi_dp_next(p);
                if (!p)
-                       return EFI_OUT_OF_RESOURCES;
+                       goto out;
        }
        fp = efi_dp_dup(p);
        if (!fp)
@@ -776,7 +969,53 @@ efi_status_t efi_dp_split_file_path(struct efi_device_path *full_path,
        p->sub_type = DEVICE_PATH_SUB_TYPE_END;
        p->length = sizeof(*p);
 
+out:
        *device_path = dp;
        *file_path = fp;
        return EFI_SUCCESS;
 }
+
+efi_status_t efi_dp_from_name(const char *dev, const char *devnr,
+                             const char *path,
+                             struct efi_device_path **device,
+                             struct efi_device_path **file)
+{
+       int is_net;
+       struct blk_desc *desc = NULL;
+       disk_partition_t fs_partition;
+       int part = 0;
+       char filename[32] = { 0 }; /* dp->str is u16[32] long */
+       char *s;
+
+       if (path && !file)
+               return EFI_INVALID_PARAMETER;
+
+       is_net = !strcmp(dev, "Net");
+       if (!is_net) {
+               part = blk_get_device_part_str(dev, devnr, &desc, &fs_partition,
+                                              1);
+               if (part < 0 || !desc)
+                       return EFI_INVALID_PARAMETER;
+
+               if (device)
+                       *device = efi_dp_from_part(desc, part);
+       } else {
+#ifdef CONFIG_NET
+               if (device)
+                       *device = efi_dp_from_eth();
+#endif
+       }
+
+       if (!path)
+               return EFI_SUCCESS;
+
+       snprintf(filename, sizeof(filename), "%s", path);
+       /* DOS style file path: */
+       s = filename;
+       while ((s = strchr(s, '/')))
+               *s++ = '\\';
+       *file = efi_dp_from_file(((!is_net && device) ? desc : NULL),
+                                part, filename);
+
+       return EFI_SUCCESS;
+}