root/sys/arch/amd64/amd64/dkcsum.c
/*      $OpenBSD: dkcsum.c,v 1.21 2014/09/14 14:17:23 jsg Exp $ */

/*-
 * Copyright (c) 1997 Niklas Hallqvist.  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.
 */

/*
 * A checksumming pseudo device used to get unique labels of each disk
 * that needs to be matched to BIOS disks.
 */

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/fcntl.h>
#include <sys/reboot.h>
#include <sys/stat.h>
#include <sys/systm.h>

#include <machine/biosvar.h>

#include <lib/libz/zlib.h>

dev_t dev_rawpart(struct device *);     /* XXX */

extern u_int32_t bios_cksumlen;
extern bios_diskinfo_t *bios_diskinfo;
extern dev_t bootdev;

void
dkcsumattach(void)
{
        struct device *dv;
        struct buf *bp;
        struct bdevsw *bdsw;
        dev_t dev, pribootdev, altbootdev;
        int error, picked;
        u_int32_t csum;
        bios_diskinfo_t *bdi, *hit;

        /* do nothing if no diskinfo passed from /boot, or a bad length */
        if (bios_diskinfo == NULL || bios_cksumlen * DEV_BSIZE > MAXBSIZE)
                return;

        /* Do nothing if bootdev is a CD drive. */
        if (B_TYPE(bootdev) == 6)
                return;

#ifdef DEBUG
        printf("dkcsum: bootdev=%#x\n", bootdev);
        for (bdi = bios_diskinfo; bdi->bios_number != -1; bdi++) {
                if (bdi->bios_number & 0x80) {
                        printf("dkcsum: BIOS drive %#x bsd_dev=%#x "
                            "checksum=%#x\n", bdi->bios_number, bdi->bsd_dev,
                            bdi->checksum);
                }
        }
#endif
        pribootdev = altbootdev = 0;

        /*
         * XXX What if DEV_BSIZE is changed to something else than the BIOS
         * blocksize?  Today, /boot doesn't cover that case so neither need
         * I care here.
         */
        bp = geteblk(bios_cksumlen * DEV_BSIZE);        /* XXX error check?  */

        TAILQ_FOREACH(dv, &alldevs, dv_list) {
                if (dv->dv_class != DV_DISK)
                        continue;
                bp->b_dev = dev = dev_rawpart(dv);
                if (dev == NODEV)
                        continue;
                bdsw = &bdevsw[major(dev)];

                /*
                 * This open operation guarantees a proper initialization
                 * of the device, for future strategy calls.
                 */
                error = (*bdsw->d_open)(dev, FREAD, S_IFCHR, curproc);
                if (error) {
                        /* XXX What to do here? */
#ifdef DEBUG
                        printf("dkcsum: %s open failed (%d)\n",
                            dv->dv_xname, error);
#endif
                        continue;
                }

                /* Read blocks to cksum.  XXX maybe a d_read should be used. */
                bp->b_blkno = 0;
                bp->b_bcount = bios_cksumlen * DEV_BSIZE;
                bp->b_error = 0; /* B_ERROR and b_error may have stale data. */
                CLR(bp->b_flags, B_READ | B_WRITE | B_DONE | B_ERROR);
                SET(bp->b_flags, B_BUSY | B_READ | B_RAW);
                (*bdsw->d_strategy)(bp);
                if ((error = biowait(bp))) {
                        /* XXX What to do here? */
#ifdef DEBUG
                        printf("dkcsum: %s read failed (%d)\n",
                            dv->dv_xname, error);
#endif
                        error = (*bdsw->d_close)(dev, 0, S_IFCHR, curproc);
#ifdef DEBUG
                        if (error)
                                printf("dkcsum: %s close failed (%d)\n",
                                    dv->dv_xname, error);
#endif
                        continue;
                }
                error = (*bdsw->d_close)(dev, FREAD, S_IFCHR, curproc);
                if (error) {
                        /* XXX What to do here? */
#ifdef DEBUG
                        printf("dkcsum: %s closed failed (%d)\n",
                            dv->dv_xname, error);
#endif
                        continue;
                }

                csum = adler32(0, bp->b_data, bios_cksumlen * DEV_BSIZE);
#ifdef DEBUG
                printf("dkcsum: %s checksum is %#x\n", dv->dv_xname, csum);
#endif

                /* Find the BIOS device */
                hit = 0;
                for (bdi = bios_diskinfo; bdi->bios_number != -1; bdi++) {
                        /* Skip non-harddrives */
                        if (!(bdi->bios_number & 0x80))
                                continue;
                        if (bdi->checksum != csum)
                                continue;
                        picked = hit || (bdi->flags & BDI_PICKED);
                        if (!picked)
                                hit = bdi;
#ifdef DEBUG
                        printf("dkcsum: %s matches BIOS drive %#x%s\n",
                            dv->dv_xname, bdi->bios_number,
                            (picked ? " IGNORED" : ""));
#endif
                }

                /*
                 * If we have no hit, that's OK, we can see a lot more devices
                 * than the BIOS can, so this case is pretty normal.
                 */
                if (!hit) {
#ifdef DEBUG
                        printf("dkcsum: %s has no matching BIOS drive\n",
                            dv->dv_xname);
#endif  
                        continue;
                }

                /*
                 * Fixup bootdev if units match.  This means that all of
                 * hd*, sd*, wd*, will be interpreted the same.  Not 100%
                 * backwards compatible, but sd* and wd* should be phased-
                 * out in the bootblocks.
                 */

                /* B_TYPE dependent hd unit counting bootblocks */
                if ((B_ADAPTOR(bootdev) == B_ADAPTOR(hit->bsd_dev)) &&
                    (B_CONTROLLER(bootdev) == B_CONTROLLER(hit->bsd_dev)) &&
                    (B_TYPE(bootdev) == B_TYPE(hit->bsd_dev)) &&
                    (B_UNIT(bootdev) == B_UNIT(hit->bsd_dev))) {
                        int type, ctrl, adap, part, unit;

                        type = major(bp->b_dev);
                        adap = B_ADAPTOR(bootdev);
                        ctrl = B_CONTROLLER(bootdev);
                        unit = DISKUNIT(bp->b_dev);
                        part = B_PARTITION(bootdev);

                        pribootdev = MAKEBOOTDEV(type, ctrl, adap, unit, part);
#ifdef DEBUG
                        printf("dkcsum: %s is primary boot disk\n",
                            dv->dv_xname);
#endif
                }
                /* B_TYPE independent hd unit counting bootblocks */
                if (B_UNIT(bootdev) == (hit->bios_number & 0x7F)) {
                        int type, ctrl, adap, part, unit;

                        type = major(bp->b_dev);
                        adap = B_ADAPTOR(bootdev);
                        ctrl = B_CONTROLLER(bootdev);
                        unit = DISKUNIT(bp->b_dev);
                        part = B_PARTITION(bootdev);

                        altbootdev = MAKEBOOTDEV(type, ctrl, adap, unit, part);
#ifdef DEBUG
                        printf("dkcsum: %s is alternate boot disk\n",
                            dv->dv_xname);
#endif
                }

                /* This will overwrite /boot's guess, just so you remember */
                hit->bsd_dev = MAKEBOOTDEV(major(bp->b_dev), 0, 0,
                    DISKUNIT(bp->b_dev), RAW_PART);
                hit->flags |= BDI_PICKED;
        }
        bootdev = pribootdev ? pribootdev : altbootdev ? altbootdev : bootdev;

        bp->b_flags |= B_INVAL;
        brelse(bp);
}