root/sys/arch/i386/stand/efiboot/devopen.c
/*      $NetBSD: devopen.c,v 1.14 2023/06/20 07:46:03 rin Exp $  */

/*-
 * Copyright (c) 2005 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Bang Jun-Young.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Copyright (c) 1996, 1997
 *      Matthias Drochner.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "efiboot.h"

#include <lib/libsa/dev_net.h>
#include <lib/libsa/net.h>

#include <biosdisk.h>
#include "devopen.h"
#include <bootinfo.h>
#include "efidisk.h"

static int
dev2bios(char *devname, int unit, int *biosdev)
{

        if (strcmp(devname, "hd") == 0)
                *biosdev = 0x80 + unit;
        else if (strcmp(devname, "cd") == 0)
                *biosdev = 0x80 + get_harddrives() + unit;
        else
                return ENXIO;

        return 0;
}

void
bios2dev(int biosdev, daddr_t sector, char **devname, int *unit,
         int *partition, const char **part_name)
{
        static char savedevname[MAXDEVNAME+1];

        *unit = biosdev & 0x7f;

        if (efi_bootdp_type == BOOT_DEVICE_TYPE_NET) {
                *devname = "net";
                *unit = efi_net_get_booted_interface_unit();
                if (*unit < 0)
                        *unit = 0;
                *partition = 0;
                return;
        } else if (biosdev >= 0x80 + get_harddrives()) {
                *devname = "cd";
                *unit -= get_harddrives();
        } else
                *devname = "hd";

        (void)biosdisk_findpartition(biosdev, sector, partition, part_name);
        if (part_name != NULL && *part_name != NULL) {
                snprintf(savedevname, sizeof(savedevname),
                    "NAME=%s", *part_name);
                *devname = savedevname;
        }
}

#if defined(SUPPORT_NFS) || defined(SUPPORT_TFTP)
const struct netboot_fstab *
netboot_fstab_find(const char *name)
{
        int i;

        if (strcmp(name, "net") == 0)
                return &netboot_fstab[0];

        for (i = 0; i < nnetboot_fstab; i++) {
                if (strcmp(name, netboot_fstab[i].name) == 0)
                        return &netboot_fstab[i];
        }

        return NULL;
}

static const struct netboot_fstab *
netboot_fstab_findn(const char *name, size_t len)
{
        int i;

        if (strncmp(name, "net", len) == 0)
                return &netboot_fstab[0];

        for (i = 0; i < nnetboot_fstab; i++) {
                if (strncmp(name, netboot_fstab[i].name, len) == 0)
                        return &netboot_fstab[i];
        }

        return NULL;
}
#endif

struct btinfo_bootpath bibp;
extern bool kernel_loaded;

/*
 * Open the EFI disk device
 */
int
devopen(struct open_file *f, const char *fname, char **file)
{
        char *fsname, *devname;
        const char *xname = NULL;
        int unit, partition;
        int biosdev;
        int error;
#if defined(SUPPORT_NFS) || defined(SUPPORT_TFTP)
        struct devdesc desc;
        const struct netboot_fstab *nf;
        char *filename;
        size_t fsnamelen;
        int i, n;
#endif

        error = parsebootfile(fname, &fsname, &devname, &unit, &partition,
            (const char **) file);
        if (error)
                return error;

        memcpy(file_system, file_system_disk,
            sizeof(struct fs_ops) * nfsys_disk);
        nfsys = nfsys_disk;

        /* Search by GPT label or raidframe name */
        if (strstr(devname, "NAME=") == devname)
                xname = devname;
        if (strstr(devname, "raid") == devname)
                xname = fname;

        if (xname != NULL) {
                f->f_dev = &devsw[0];           /* must be biosdisk */

                if (!kernel_loaded) {
                        strncpy(bibp.bootpath, *file, sizeof(bibp.bootpath));
                        BI_ADD(&bibp, BTINFO_BOOTPATH, sizeof(bibp));
                }

                error = biosdisk_open_name(f, xname);
                return error;
        }

        /*
         * Network
         */
#if defined(SUPPORT_NFS) || defined(SUPPORT_TFTP)
        nf = netboot_fstab_find(devname);
        if (nf != NULL) {
                n = 0;
                if (strcmp(devname, "net") == 0) {
                        for (i = 0; i < nnetboot_fstab; i++) {
                                memcpy(&file_system[n++], netboot_fstab[i].ops,
                                    sizeof(struct fs_ops));
                        }
                } else {
                        memcpy(&file_system[n++], nf->ops,
                            sizeof(struct fs_ops));
                }
                nfsys = n;

#ifdef SUPPORT_BOOTP
                try_bootp = 1;
#endif

                /* If we got passed a filename, pass it to the BOOTP server. */
                if (fname) {
                        filename = strchr(fname, ':');
                        if (filename != NULL)
                                filename++;
                        else
                                filename = (char *)fname;
                        strlcpy(bootfile, filename, sizeof(bootfile));
                }

                memset(&desc, 0, sizeof(desc));
                strlcpy(desc.d_name, "net", sizeof(desc.d_name));
                desc.d_unit = unit;

                f->f_dev = &devsw[1];           /* must be net */
                if (!kernel_loaded) {
                        strncpy(bibp.bootpath, *file, sizeof(bibp.bootpath));
                        BI_ADD(&bibp, BTINFO_BOOTPATH, sizeof(bibp));
                }
                error = DEV_OPEN(f->f_dev)(f, &desc);
                if (error)
                        return error;

                /*
                 * If the DHCP server provided a file name:
                 * - If it contains a ":", assume it points to a NetBSD kernel.
                 * - If not, assume that the DHCP server was not able to pass
                 *   a separate filename for the kernel. (The name probably was
                 *   the same as used to load "efiboot".) Ignore it and use
                 *   the default in this case.
                 * So we cater to simple DHCP servers while being able to use
                 * the power of conditional behaviour in modern ones.
                 */
                filename = strchr(bootfile, ':');
                if (filename != NULL) {
                        fname = bootfile;

                        fsnamelen = filename - fname;
                        nf = netboot_fstab_findn(fname, fsnamelen);
                        if (nf == NULL ||
                            strncmp(fname, "net", fsnamelen) == 0) {
                                printf("Invalid file system type specified in "
                                    "%s\n", fname);
                                error = EINVAL;
                                goto neterr;
                        }

                        memcpy(file_system, nf->ops, sizeof(struct fs_ops));
                        nfsys = 1;
                }

                filename = fname ? strchr(fname, ':') : NULL;
                if (filename != NULL) {
                        filename++;
                        if (*filename == '\0') {
                                printf("No file specified in %s\n", fname);
                                error = EINVAL;
                                goto neterr;
                        }
                } else
                        filename = (char *)fname;

                *file = filename;
                return 0;

neterr:
                DEV_CLOSE(f->f_dev)(f);
                f->f_dev = NULL;
                return error;
        }
#endif

        /*
         * biosdisk
         */
        if (strcmp(devname, "esp") == 0) {
                bios2dev(boot_biosdev, boot_biossector, &devname, &unit,
                    &partition, NULL);
                if (efidisk_get_efi_system_partition(boot_biosdev, &partition))
                        return ENXIO;
        }

        error = dev2bios(devname, unit, &biosdev);
        if (error)
                return error;

        f->f_dev = &devsw[0];           /* must be biosdisk */

        if (!kernel_loaded) {
                strncpy(bibp.bootpath, *file, sizeof(bibp.bootpath));
                BI_ADD(&bibp, BTINFO_BOOTPATH, sizeof(bibp));
        }

        return biosdisk_open(f, biosdev, partition);
}