root/sys/arch/sparc/stand/ofwboot/ofdev.c
/*      $NetBSD: ofdev.c,v 1.38 2026/06/21 10:28:08 andvar Exp $        */

/*
 * Copyright (C) 1995, 1996 Wolfgang Solfrank.
 * Copyright (C) 1995, 1996 TooLs GmbH.
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by TooLs GmbH.
 * 4. The name of TooLs GmbH may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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.
 */
/*
 * Device I/O routines using Open Firmware
 */
#include <sys/param.h>
#include <sys/disklabel.h>
#ifdef NETBOOT
#include <netinet/in.h>
#endif

#include <lib/libsa/stand.h>
#include <lib/libsa/ufs.h>
#include <lib/libsa/lfs.h>
#include <lib/libsa/cd9660.h>
#ifdef NETBOOT
#include <lib/libsa/nfs.h>
#include <lib/libsa/tftp.h>
#endif
#include <lib/libkern/libkern.h>

#include <dev/sun/disklabel.h>
#include <dev/raidframe/raidframevar.h>

#include <machine/promlib.h>

#include "ofdev.h"
#include "boot.h"
#include "net.h"

extern char bootdev[];
extern bool root_fs_quickseekable;

struct btinfo_bootdev_unit bi_unit;
bool bootinfo_pass_bootunit = false;

/*
 * This is ugly.  A path on a sparc machine is something like this:
 *
 *      [device] [-<options] [path] [-options] [otherstuff] [-<more options]
 *
 */

char *
filename(char *str, char *ppart)
{
        char *cp, *lp;
        char savec;
        int dhandle;
        char devtype[16];

        lp = str;
        devtype[0] = 0;
        *ppart = '\0';
        for (cp = str; *cp; lp = cp) {
                /* For each component of the path name... */
                while (*++cp && *cp != '/');
                savec = *cp;
                *cp = 0;
                /* ...look whether there is a device with this name */
                dhandle = prom_finddevice(str);
                DPRINTF(("filename: prom_finddevice(%s) returned %x\n",
                       str, dhandle));
                *cp = savec;
                if (dhandle == -1) {
                        /*
                         * if not, lp is the delimiter between device and
                         * path.  if the last component was a block device.
                         */
                        if (strcmp(devtype, "block") == 0
                            || strcmp(devtype, "scsi") == 0) {
                                /* search for arguments */
                                DPRINTF(("filename: hunting for arguments "
                                       "in %s\n", lp));
                                for (cp = lp; ; ) {
                                        cp--;
                                        if (cp < str ||
                                            cp[0] == '/' ||
                                            (cp[0] == ' ' && (cp+1) != lp &&
                                             cp[1] == '-'))
                                                break;
                                }
                                if (cp >= str && *cp == '-')
                                        /* found arguments, make firmware
                                           ignore them */
                                        *cp = 0;
                                for (cp = lp; *--cp && *cp != ',' 
                                        && *cp != ':';)
                                                ;
                                if (cp[0] == ':' && cp[1] >= 'a' &&
                                    cp[1] <= 'a' + MAXPARTITIONS) {
                                        *ppart = cp[1];
                                        cp[0] = '\0';
                                }
                        }
                        DPRINTF(("filename: found %s\n",lp));
                        return lp;
                } else if (_prom_getprop(dhandle, "device_type", devtype,
                                sizeof devtype) < 0)
                        devtype[0] = 0;
        }
        DPRINTF(("filename: not found\n"));
        return 0;
}

static int
strategy(void *devdata, int rw, daddr_t blk, size_t size, void *buf, size_t *rsize)
{
        struct of_dev *dev = devdata;
        u_quad_t pos;
        int n;

        if (rw != F_READ)
                return EPERM;
        if (dev->type != OFDEV_DISK)
                panic("strategy");

#ifdef NON_DEBUG
        printf("strategy: block %lx, partition offset %lx, blksz %lx\n",
               (long)blk, (long)dev->partoff, (long)dev->bsize);
        printf("strategy: seek position should be: %lx\n",
               (long)((blk + dev->partoff) * dev->bsize));
#endif
        pos = (u_quad_t)(blk + dev->partoff) * dev->bsize;

        for (;;) {
#ifdef NON_DEBUG
                printf("strategy: seeking to %lx\n", (long)pos);
#endif
                if (prom_seek(dev->handle, pos) < 0)
                        break;
#ifdef NON_DEBUG
                printf("strategy: reading %lx at %p\n", (long)size, buf);
#endif
                n = prom_read(dev->handle, buf, size);
                if (n == -2)
                        continue;
                if (n < 0)
                        break;
                *rsize = n;
                return 0;
        }
        return EIO;
}

static int
devclose(struct open_file *of)
{
        struct of_dev *op = of->f_devdata;

#ifdef NETBOOT
        if (op->type == OFDEV_NET)
                net_close(op);
#endif
        prom_close(op->handle);
        op->handle = -1;
        return 0;
}

static struct devsw ofdevsw[1] = {
        {
                "OpenFirmware",
                strategy,
                (int (*)(struct open_file *, ...))nodev,
                devclose,
                noioctl
        }
};
int ndevs = sizeof ofdevsw / sizeof ofdevsw[0];


#ifdef SPARC_BOOT_UFS
static struct fs_ops file_system_ufs[] = 
{ FS_OPS(ufs), FS_OPS(ffsv2), FS_OPS(lfsv1), FS_OPS(lfsv2) };
#endif
#ifdef SPARC_BOOT_CD9660
static struct fs_ops file_system_cd9660 = FS_OPS(cd9660);
#endif
#ifdef NETBOOT
static struct fs_ops file_system_nfs = FS_OPS(nfs);
static struct fs_ops file_system_tftp = FS_OPS(tftp);
#endif

struct fs_ops file_system[7];
int nfsys;

static struct of_dev ofdev = {
        -1,
};

char opened_name[256];
int floppyboot;

/************************************************************************
 *
 * The rest of this was taken from arch/sparc64/scsi/sun_disklabel.c
 * and then substantially rewritten by Gordon W. Ross
 *
 ************************************************************************/

/* What partition types to assume for Sun disklabels: */
static u_char
sun_fstypes[8] = {
        FS_BSDFFS,      /* a */
        FS_SWAP,        /* b */
        FS_OTHER,       /* c - whole disk */
        FS_BSDFFS,      /* d */
        FS_BSDFFS,      /* e */
        FS_BSDFFS,      /* f */
        FS_BSDFFS,      /* g */
        FS_BSDFFS,      /* h */
};

/*
 * Given a SunOS disk label, set lp to a BSD disk label.
 * Returns NULL on success, else an error string.
 *
 * The BSD label is cleared out before this is called.
 */
static char *
disklabel_sun_to_bsd(char *cp, struct disklabel *lp)
{
        struct sun_disklabel *sl;
        struct partition *npp;
        struct sun_dkpart *spp;
        int i, secpercyl;
        u_short cksum, *sp1, *sp2;

        sl = (struct sun_disklabel *)cp;

        /* Verify the XOR check. */
        sp1 = (u_short *)sl;
        sp2 = (u_short *)(sl + 1);
        cksum = 0;
        while (sp1 < sp2)
                cksum ^= *sp1++;
        if (cksum != 0)
                return("SunOS disk label, bad checksum");

        /* Format conversion. */
        lp->d_magic = DISKMAGIC;
        lp->d_magic2 = DISKMAGIC;
        memcpy(lp->d_packname, sl->sl_text, sizeof(lp->d_packname));

        lp->d_secsize = 512;
        lp->d_nsectors   = sl->sl_nsectors;
        lp->d_ntracks    = sl->sl_ntracks;
        lp->d_ncylinders = sl->sl_ncylinders;

        secpercyl = sl->sl_nsectors * sl->sl_ntracks;
        lp->d_secpercyl  = secpercyl;
        lp->d_secperunit = secpercyl * sl->sl_ncylinders;

        lp->d_sparespercyl = sl->sl_sparespercyl;
        lp->d_acylinders   = sl->sl_acylinders;
        lp->d_rpm          = sl->sl_rpm;
        lp->d_interleave   = sl->sl_interleave;

        lp->d_npartitions = 8;
        /* These are as defined in <ufs/ffs/fs.h> */
        lp->d_bbsize = 8192;    /* XXX */
        lp->d_sbsize = 8192;    /* XXX */

        for (i = 0; i < 8; i++) {
                spp = &sl->sl_part[i];
                npp = &lp->d_partitions[i];
                npp->p_offset = spp->sdkp_cyloffset * secpercyl;
                npp->p_size = spp->sdkp_nsectors;
                DPRINTF(("partition %d start %x size %x\n", i, (int)npp->p_offset, (int)npp->p_size));
                if (npp->p_size == 0) {
                        npp->p_fstype = FS_UNUSED;
                } else {
                        npp->p_fstype = sun_fstypes[i];
                        if (npp->p_fstype == FS_BSDFFS) {
                                /*
                                 * The sun label does not store the FFS fields,
                                 * so just set them with default values here.
                                 */
                                npp->p_fsize = 1024;
                                npp->p_frag = 8;
                                npp->p_cpg = 16;
                        }
                }
        }

        lp->d_checksum = 0;
        lp->d_checksum = dkcksum(lp);
        DPRINTF(("disklabel_sun_to_bsd: success!\n"));
        return (NULL);
}

/*
 * Find a valid disklabel.
 */
static char *
search_label(struct of_dev *devp, u_long off, char *buf,
             struct disklabel *lp, u_long off0)
{
        size_t readsize;
        struct disklabel *dlp;
        struct sun_disklabel *slp;

        /* minimal requirements for archetypal disk label */
        if (lp->d_secperunit == 0)
                lp->d_secperunit = 0x1fffffff;
        lp->d_npartitions = 1;
        if (lp->d_partitions[0].p_size == 0)
                lp->d_partitions[0].p_size = 0x1fffffff;
        lp->d_partitions[0].p_offset = 0;

        if (strategy(devp, F_READ, LABELSECTOR, DEV_BSIZE, buf, &readsize)
            || readsize != DEV_BSIZE)
                return ("Cannot read label");
        /* Check for a NetBSD disk label. */
        dlp = (struct disklabel *) (buf + LABELOFFSET);
        if (dlp->d_magic == DISKMAGIC) {
                if (dkcksum(dlp))
                        return ("NetBSD disk label corrupted");
                *lp = *dlp;
                DPRINTF(("search_label: found NetBSD label\n"));
                return (NULL);
        }

        /* Check for a Sun disk label (for PROM compatibility). */
        slp = (struct sun_disklabel *) buf;
        if (slp->sl_magic == SUN_DKMAGIC)
                return (disklabel_sun_to_bsd(buf, lp));


        memset(buf, 0, DEV_BSIZE);
        return ("no disk label");
}

static void
device_target_unit(const char *dev, int ihandle)
{
        cell_t units[4], phandle, myself, depth = 0, odepth = 0, cnt;
        char buf[256];

        /* init the data passed to the kernel */
        bootinfo_pass_bootunit = false;
        memset(&bi_unit, 0, sizeof(bi_unit));

        /* save old my-self value */
        OF_interpret("my-self", 0, 1, &myself);
        /* set our device as my-self */
        OF_interpret("to my-self", 1, 0, HDL2CELL(ihandle));

        /*
         * my-unit delivers a variable number of cells, we could
         * walk up the path and find a #address-cells value that
         * describes it, but it seems to just work this simple
         * way.
         */
        OF_interpret("depth", 0, 1, &odepth);   
        OF_interpret("my-unit depth", 0, 5, &depth,
            &units[0], &units[1], &units[2], &units[3]);
        cnt = depth-odepth;

        /*
         * Old versions of QEMU's OpenBIOS have a bug in the
         * CIF implementation for instance_to_package, test
         * for that explicitly here and work around it if needed.
         */
        phandle = OF_instance_to_package(ihandle);      
        OF_package_to_path(phandle, buf, sizeof(buf));
        buf[sizeof(buf)-1] = 0;
        if (strlen(buf) > 2 && strlen(dev) > 2 &&
            strncmp(buf, dev, strlen(buf)) != 0) {
                DPRINTF(("OpenBIOS workaround: phandle %" PRIx32 "is %s, "
                    "does not match %s\n", (uint32_t)phandle, buf, dev));
                OF_interpret("my-self ihandle>non-interposed-phandle",
                     0, 1, &phandle);
                OF_package_to_path(phandle, buf, sizeof(buf));
                DPRINTF(("new phandle %" PRIx32 " is %s\n",
                    (uint32_t)phandle, buf));
        }

        bi_unit.phandle = phandle;
        bi_unit.parent = OF_parent(phandle);
        bi_unit.lun = units[cnt > 2 ? 3 : 1];
        bi_unit.target = units[cnt > 2 ? 2 : 0];
        if (cnt >= 4)
                bi_unit.wwn = (uint64_t)units[0] << 32 | (uint32_t)units[1];
        DPRINTF(("boot device package: %" PRIx32 ", parent: %" PRIx32 
            ", lun: %" PRIu32 ", target: %" PRIu32 ", wwn: %" PRIx64 "\n",
            bi_unit.phandle, bi_unit.parent, bi_unit.lun, bi_unit.target,
            bi_unit.wwn));

        /* restore my-self */
        OF_interpret("to my-self", 1, 0, myself);

        /* now that we have gatherd all the details, pass them to the kernel */
        bootinfo_pass_bootunit = true;
}

int
devopen(struct open_file *of, const char *name, char **file)
{
        char *cp;
        char partition;
        char fname[256], devname[256];
        union {
                char buf[DEV_BSIZE];
                struct disklabel label;
        } b;
        struct disklabel label;
        int handle, part, try = 0;
        size_t readsize;
        char *errmsg = NULL, *pp = NULL, savedpart = 0;
        int error = 0;
        bool get_target_unit = false;

        if (ofdev.handle != -1)
                panic("devopen: ofdev already in use");
        if (of->f_flags != F_READ)
                return EPERM;
        DPRINTF(("devopen: you want %s\n", name));
        strcpy(fname, name);
        cp = filename(fname, &partition);
        if (cp) {
                strcpy(b.buf, cp);
                *cp = 0;
        }
        if (!cp || !b.buf[0])
                strcpy(b.buf, DEFAULT_KERNEL);
        if (!*fname)
                strcpy(fname, bootdev);
        strcpy(opened_name, fname);
        if (partition) {
                cp = opened_name + strlen(opened_name);
                *cp++ = ':';
                *cp++ = partition;
                *cp = 0;
        }
        *file = opened_name + strlen(opened_name);
        if (b.buf[0] != '/')
                strcat(opened_name, "/");
        strcat(opened_name, b.buf);
        DPRINTF(("devopen: trying %s\n", fname));
        if ((handle = prom_finddevice(fname)) == -1)
                return ENOENT;
        DPRINTF(("devopen: found %s\n", fname));
        if (_prom_getprop(handle, "name", b.buf, sizeof b.buf) < 0)
                return ENXIO;
        DPRINTF(("devopen: %s is called %s\n", fname, b.buf));
        floppyboot = !strcmp(b.buf, "floppy");
        if (_prom_getprop(handle, "device_type", b.buf, sizeof b.buf) < 0)
                return ENXIO;
        DPRINTF(("devopen: %s is a %s device\n", fname, b.buf));
        if (strcmp(b.buf, "block") == 0 || strcmp(b.buf, "scsi") == 0) {

                get_target_unit = true;

                pp = strrchr(fname, ':');
                if (pp && pp[1] >= 'a' && pp[1] <= 'f' && pp[2] == 0) {
                        savedpart = pp[1];
                } else {
                        savedpart = 'a';
                        handle = prom_open(fname);
                        if (handle != -1) {
                                OF_instance_to_path(handle, devname,
                                    sizeof(devname));
                                DPRINTF(("real path: %s\n", devname));
                                prom_close(handle);
                                pp = devname + strlen(devname);
                                if (pp > devname + 3) pp -= 2;
                                if (pp[0] == ':')
                                        savedpart = pp[1];
                        }
                        pp = fname + strlen(fname);
                        pp[0] = ':';
                        pp[2] = '\0';
                }
                pp[1] = 'c';
                DPRINTF(("devopen: replacing by whole disk device %s\n",
                    fname));
                if (savedpart)
                        partition = savedpart;
        }

open_again:
        DPRINTF(("devopen: opening %s\n", fname));
        if ((handle = prom_open(fname)) == -1) {
                DPRINTF(("devopen: open of %s failed\n", fname));
                if (pp && savedpart) {
                        if (try == 0) {
                                pp[0] = '\0';
                                try = 1;
                        } else {
                                pp[0] = ':';
                                pp[1] = savedpart;
                                pp = NULL;
                                savedpart = '\0';
                        }
                        goto open_again;
                }
                return ENXIO;
        }
        DPRINTF(("devopen: %s is now open\n", fname));

        if (get_target_unit)
                device_target_unit(fname, handle);

        memset(&ofdev, 0, sizeof ofdev);
        ofdev.handle = handle;
        if (strcmp(b.buf, "block") == 0 || strcmp(b.buf, "scsi") == 0) {
                ofdev.type = OFDEV_DISK;
                ofdev.bsize = DEV_BSIZE;
                /* First try to find a disklabel without MBR partitions */
                DPRINTF(("devopen: trying to read disklabel\n"));
                if (strategy(&ofdev, F_READ,
                             LABELSECTOR, DEV_BSIZE, b.buf, &readsize) != 0
                    || readsize != DEV_BSIZE
                    || (errmsg = getdisklabel(b.buf, &label))) {
                        if (errmsg) {
                                DPRINTF(("devopen: getdisklabel returned %s\n",
                                        errmsg));
                        }
                        /* Else try MBR partitions */
                        errmsg = search_label(&ofdev, 0, b.buf, &label, 0);
                        if (errmsg) {
                                printf("devopen: search_label returned %s\n", errmsg);
                                error = ERDLAB;
                        }
                        if (error && error != ERDLAB)
                                goto bad;
                }

                if (error == ERDLAB) {
                        /* No, label, just use complete disk */
                        ofdev.partoff = 0;
                        if (pp && savedpart) {
                                pp[1] = savedpart;
                                prom_close(handle);
                                if ((handle = prom_open(fname)) == -1) {
                                        DPRINTF(("devopen: open of %s failed\n",
                                                fname));
                                        return ENXIO;
                                }
                                ofdev.handle = handle;
                                DPRINTF(("devopen: back to original device %s\n",
                                        fname));
                        }
                } else {
                        part = partition ? partition - 'a' : 0;
                        ofdev.partoff = label.d_partitions[part].p_offset;
                        DPRINTF(("devopen: setting partition %d offset %lx\n",
                               part, ofdev.partoff));
                        if (label.d_partitions[part].p_fstype == FS_RAID) {
                                ofdev.partoff += RF_PROTECTED_SECTORS;
                                DPRINTF(("devopen: found RAID partition, "
                                    "adjusting offset to %lx\n", ofdev.partoff));
                        }
                }

                nfsys = 0;
                of->f_dev = ofdevsw;
                of->f_devdata = &ofdev;
#ifdef SPARC_BOOT_UFS
                memcpy(&file_system[nfsys++], &file_system_ufs[0], sizeof file_system[0]);
                memcpy(&file_system[nfsys++], &file_system_ufs[1], sizeof file_system[0]);
                memcpy(&file_system[nfsys++], &file_system_ufs[2], sizeof file_system[0]);
                memcpy(&file_system[nfsys++], &file_system_ufs[3], sizeof file_system[0]);
#endif
#ifdef SPARC_BOOT_CD9660
                memcpy(&file_system[nfsys++], &file_system_cd9660, sizeof file_system[0]);
#endif
                DPRINTF(("devopen: return 0\n"));
                return 0;
        }
#ifdef NETBOOT
        if (!strcmp(b.buf, "network")) {
                if ((error = net_open(&ofdev)) != 0)
                        goto bad;

                ofdev.type = OFDEV_NET;
                of->f_dev = ofdevsw;
                of->f_devdata = &ofdev;

                if (!strncmp(*file,"/tftp:",6)) {
                        *file += 6;
                        memcpy(&file_system[0], &file_system_tftp, sizeof file_system[0]);
                        if (net_tftp_bootp((int **)&of->f_devdata)) {
                                net_close(&ofdev);
                                goto bad;
                        }
                        root_fs_quickseekable = false;
                } else {
                        memcpy(&file_system[0], &file_system_nfs, sizeof file_system[0]);
                        if ((error = net_mountroot()) != 0) {
                                net_close(&ofdev);
                                goto bad;
                        }
                }
                nfsys = 1;
                return 0;
        }
#endif
        error = EFTYPE;
bad:
        DPRINTF(("devopen: error %d, cannot open device\n", error));
        prom_close(handle);
        ofdev.handle = -1;
        return error;
}