root/sys/arch/hp300/hp300/autoconf.c
/*      $NetBSD: autoconf.c,v 1.119 2025/11/29 19:32:52 thorpej Exp $   */

/*-
 * Copyright (c) 1996, 1997, 2002 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe.
 *
 * 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) 1988 University of Utah.
 * Copyright (c) 1982, 1986, 1990, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the Systems Programming Group of the University of Utah Computer
 * Science Department.
 *
 * Copyright (c) 1992, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This software was developed by the Computer Systems Engineering group
 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
 * contributed to Berkeley.
 *
 * All advertising materials mentioning features or use of this software
 * must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Lawrence Berkeley Laboratory.
 *
 * 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. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 * from: Utah $Hdr: autoconf.c 1.36 92/12/20$
 *
 *      @(#)autoconf.c  8.2 (Berkeley) 1/12/94
 */

/*
 * Setup the system to run on the current machine.
 *
 * Configure() is called at boot time.  Available
 * devices are determined (from possibilities mentioned in ioconf.c),
 * and the drivers are initialized.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: autoconf.c,v 1.119 2025/11/29 19:32:52 thorpej Exp $");

#include "dvbox.h"
#include "gbox.h"
#include "hyper.h"
#include "rbox.h"
#include "topcat.h"
#include "tvrx.h"
#include "gendiofb.h"
#include "sti_dio.h"
#include "sti_sgc.h"
#include "com_dio.h"
#include "com_frodo.h"
#include "dcm.h"

#define _M68K_INTR_PRIVATE

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/kmem.h>
#include <sys/mount.h>
#include <sys/queue.h>
#include <sys/reboot.h>
#include <sys/tty.h>
#include <sys/vmem.h>
#include <sys/vmem_impl.h>

#include <uvm/uvm_extern.h>

#include <dev/cons.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsdisplayvar.h>
#include <dev/rasops/rasops.h>

#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsiconf.h>

#include <machine/autoconf.h>
#include <machine/vmparam.h>
#include <machine/cpu.h>
#include <machine/hp300spu.h>
#include <machine/intr.h>
#include <machine/pte.h>

#include <hp300/dev/dioreg.h>
#include <hp300/dev/diovar.h>
#include <hp300/dev/diodevs.h>

#include <hp300/dev/intioreg.h>
#include <hp300/dev/dmavar.h>
#include <hp300/dev/frodoreg.h>

#include <hp300/dev/hpibvar.h>

#if NCOM_DIO > 0
#include <hp300/dev/com_diovar.h>
#endif
#if NCOM_FRODO > 0
#include <hp300/dev/com_frodovar.h>
#endif

#if NSTI_DIO > 0 || NSTI_SGC > 0
#include <hp300/dev/sti_machdep.h>
#endif
#if NSTI_DIO > 0
#include <hp300/dev/sti_diovar.h>
#endif
#if NSTI_SGC > 0
#include <hp300/dev/sgcreg.h>
#include <hp300/dev/sgcvar.h>
#include <hp300/dev/sti_sgcvar.h>
#endif

#include <hp300/dev/diofbreg.h>
#include <hp300/dev/diofbvar.h>

/* should go away with a cleanup */
extern int dcmcnattach(bus_space_tag_t, bus_addr_t, int);
extern int dnkbdcnattach(bus_space_tag_t, bus_addr_t);

static int      dio_scan(int (*func)(bus_space_tag_t, bus_addr_t, int));
static int      dio_scode_probe(int,
                    int (*func)(bus_space_tag_t, bus_addr_t, int));

/* How we were booted. */
u_int   bootdev;

/*
 * Vmem arena for external I/O (DIO/DIO-II) address space (allocated
 * in pmap_bootstrap1()).  We allocate sufficient boundary tags for
 * 8 regions.
 */
#define EXTIO_BTAG_COUNT        VMEM_EST_BTCOUNT(1, 8)
static struct vmem extio_arena_store;
static struct vmem_btag extio_btag_store[EXTIO_BTAG_COUNT];
vmem_t *extio_arena;

/*
 * This information is built during the autoconfig process.
 * A little explanation about the way this works is in order.
 *
 *      device_register() links all devices into dev_data_list.
 *      If the device is an hpib controller, it is also linked
 *      into dev_data_list_hpib.  If the device is a scsi controller,
 *      it is also linked into dev_data_list_scsi.
 *
 *      dev_data_list_hpib and dev_data_list_scsi are sorted
 *      by select code, from lowest to highest.
 *
 *      After autoconfiguration is complete, we need to determine
 *      which device was the boot device.  The boot block assigns
 *      controller unit numbers in order of select code.  Thus,
 *      providing the controller is configured in the kernel, we
 *      can determine our version of controller unit number from
 *      the sorted hpib/scsi list.
 *
 *      At this point, we know the controller (device type
 *      encoded in bootdev tells us "scsi disk", or "hpib tape",
 *      etc.).  The next step is to find the device which
 *      has the following properties:
 *
 *              - A child of the boot controller.
 *              - Same slave as encoded in bootdev.
 *              - Same physical unit as encoded in bootdev.
 *
 *      Later, after we've set the root device in stone, we
 *      reverse the process to re-encode bootdev so it can be
 *      passed back to the boot block.
 */
struct dev_data {
        LIST_ENTRY(dev_data)    dd_list;  /* dev_data_list */
        LIST_ENTRY(dev_data)    dd_clist; /* ctlr list */
        device_t                dd_dev;  /* device described by this entry */
        int                     dd_scode; /* select code of device */
        int                     dd_slave; /* ...or slave */
        int                     dd_punit; /* and punit... */
};
typedef LIST_HEAD(, dev_data) ddlist_t;
static ddlist_t dev_data_list;          /* all dev_datas */
static ddlist_t dev_data_list_hpib;     /* hpib controller dev_datas */
static ddlist_t dev_data_list_scsi;     /* scsi controller dev_datas */

static void     findbootdev(void);
static void     findbootdev_slave(ddlist_t *, int, int, int);
static void     setbootdev(void);

static struct dev_data *dev_data_lookup(device_t);
static void     dev_data_insert(struct dev_data *, ddlist_t *);

static int      mainbusmatch(device_t, cfdata_t, void *);
static void     mainbusattach(device_t, device_t, void *);
static int      mainbussearch(device_t, cfdata_t, const int *, void *);

CFATTACH_DECL_NEW(mainbus, 0,
    mainbusmatch, mainbusattach, NULL, NULL);

static int
mainbusmatch(device_t parent, cfdata_t cf, void *aux)
{
        static int mainbus_matched = 0;

        /* Allow only one instance. */
        if (mainbus_matched)
                return 0;

        mainbus_matched = 1;
        return 1;
}

static void
mainbusattach(device_t parent, device_t self, void *aux)
{

        aprint_normal("\n");

        /* Search for and attach children. */
        config_search(self, NULL,
            CFARGS(.search = mainbussearch));
}

static int
mainbussearch(device_t parent, cfdata_t cf, const int *ldesc, void *aux)
{

        if (config_probe(parent, cf, NULL))
                config_attach(parent, cf, NULL, NULL, CFARGS_NONE);
        return 0;
}

/*
 * hp300 systems need to track all DIO interrupt handlers on a single
 * list in order to compute the auto-vector interrupt level the DMA
 * controller should interrupt at.  So, we provide a custom allocator
 * for the common interrupt dispatch code that allocates us handles
 * with linkage for this list.
 */
static struct m68k_intrhand *
hp300_ih_alloc(int km_flag)
{
        return kmem_zalloc(sizeof(struct hp300_intrhand), km_flag);
}

static void
hp300_ih_free(struct m68k_intrhand *ih)
{
        kmem_free(ih, sizeof(struct hp300_intrhand));
}

static const struct m68k_ih_allocfuncs hp300_ih_allocfuncs = {
        .alloc = hp300_ih_alloc,
        .free  = hp300_ih_free,
};

/*
 * Determine the device configuration for the running system.
 */
void
cpu_configure(void)
{

        /*
         * Initialize the dev_data_lists.
         */
        LIST_INIT(&dev_data_list);
        LIST_INIT(&dev_data_list_hpib);
        LIST_INIT(&dev_data_list_scsi);

        /* Kick off autoconfiguration. */
        (void)splhigh();

        /* Initialize the interrupt handlers. */
        m68k_intr_init(&hp300_ih_allocfuncs);

        if (config_rootfound("mainbus", NULL) == NULL)
                panic("no mainbus found");

        /* Configuration is finished, turn on interrupts. */
        (void)spl0();
}

/**********************************************************************
 * Code to find and set the boot device
 **********************************************************************/

void
cpu_rootconf(void)
{
        struct dev_data *dd;
        struct vfsops *vops;

        /*
         * Find boot device.
         */
        if ((bootdev & B_MAGICMASK) != B_DEVMAGIC) {
                printf("WARNING: boot program didn't supply boot device.\n");
                printf("Please update your boot program.\n");
        } else {
                findbootdev();
                if (booted_device == NULL) {
                        printf("WARNING: can't find match for bootdev:\n");
                        printf(
                    "type = %d, ctlr = %d, slave = %d, punit = %d, part = %d\n",
                            B_TYPE(bootdev), B_ADAPTOR(bootdev),
                            B_CONTROLLER(bootdev), B_UNIT(bootdev),
                            B_PARTITION(bootdev));
                        bootdev = 0;            /* invalidate bootdev */
                } else {
                        printf("boot device: %s\n", device_xname(booted_device));
                }
        }

        /*
         * If wild carded root device and wired down NFS root file system,
         * pick the network interface device to use.
         */
        if (rootspec == NULL) {
                vops = vfs_getopsbyname(MOUNT_NFS);
                if (vops != NULL && vops->vfs_mountroot != NULL &&
                    strcmp(rootfstype, MOUNT_NFS) == 0) {
                        for (dd = LIST_FIRST(&dev_data_list);
                            dd != NULL; dd = LIST_NEXT(dd, dd_list)) {
                                if (device_class(dd->dd_dev) == DV_IFNET) {
                                        /* Got it! */
                                        booted_device = dd->dd_dev;
                                        break;
                                }
                        }
                        if (dd == NULL) {
                                printf("no network interface for NFS root");
                        }
                }
                if (vops != NULL)
                        vfs_delref(vops);
        }

        /*
         * If bootdev is bogus, ask the user anyhow.
         */
        if (bootdev == 0)
                boothowto |= RB_ASKNAME;

        /*
         * If we booted from tape, ask the user.
         */
        if (booted_device != NULL && device_class(booted_device) == DV_TAPE)
                boothowto |= RB_ASKNAME;

        rootconf();

        /*
         * Set bootdev based on what we found as the root.
         * This is given to the boot program when we reboot.
         */
        setbootdev();

}

/*
 * Register a device.  We're passed the device and the arguments
 * used to attach it.  This is used to find the boot device.
 */
void
device_register(device_t dev, void *aux)
{
        struct dev_data *dd;
        static int seen_netdevice = 0;

        /*
         * Allocate a dev_data structure and fill it in.
         * This means making some tests twice, but we don't
         * care; this doesn't really have to be fast.
         *
         * Note that we only really care about devices that
         * we can mount as root.
         */

        dd = kmem_zalloc(sizeof(*dd), KM_SLEEP);
        dd->dd_dev = dev;

        /*
         * BOOTROM and boot program can really only understand
         * using the lowest select code network interface,
         * so we ignore all but the first.
         */
        if (device_class(dev) == DV_IFNET && seen_netdevice == 0) {
                struct dio_attach_args *da = aux;

                seen_netdevice = 1;
                dd->dd_scode = da->da_scode;
                goto linkup;
        }

        if (device_is_a(dev, "fhpib") ||
            device_is_a(dev, "nhpib") ||
            device_is_a(dev, "spc")) {
                struct dio_attach_args *da = aux;

                dd->dd_scode = da->da_scode;
                goto linkup;
        }

        if (device_is_a(dev, "rd")) {
                struct hpibbus_attach_args *ha = aux;

                dd->dd_slave = ha->ha_slave;
                dd->dd_punit = ha->ha_punit;
                goto linkup;
        }

        if (device_is_a(dev, "sd") ||
            device_is_a(dev, "cd")) {
                struct scsipibus_attach_args *sa = aux;

                dd->dd_slave = sa->sa_periph->periph_target;
                dd->dd_punit = sa->sa_periph->periph_lun;
                goto linkup;
        }

        /*
         * Didn't need the dev_data.
         */
        kmem_free(dd, sizeof(*dd));
        return;

 linkup:
        LIST_INSERT_HEAD(&dev_data_list, dd, dd_list);

        if (device_is_a(dev, "fhpib") ||
            device_is_a(dev, "nhpib")) {
                dev_data_insert(dd, &dev_data_list_hpib);
                return;
        }

        if (device_is_a(dev, "spc")) {
                dev_data_insert(dd, &dev_data_list_scsi);
                return;
        }
}

static void
findbootdev(void)
{
        int type, ctlr, slave, punit, part;
        int scsiboot, hpibboot, netboot;
        struct dev_data *dd;

        booted_device = NULL;
        booted_partition = 0;

        if ((bootdev & B_MAGICMASK) != B_DEVMAGIC)
                return;

        type  = B_TYPE(bootdev);
        ctlr  = B_ADAPTOR(bootdev);
        slave = B_CONTROLLER(bootdev);
        punit = B_UNIT(bootdev);
        part  = B_PARTITION(bootdev);

        scsiboot = (type == 4);                 /* sd or cd */
        hpibboot = (type == 0 || type == 2);    /* ct/rd */
        netboot  = (type == 6);                 /* le - special */

        /*
         * Check for network boot first, since it's a little
         * different.  The BOOTROM/boot program can only boot
         * off of the first (lowest select code) ethernet
         * device.  device_register() knows this and only
         * registers one DV_IFNET.  This is a safe assumption
         * since the code that finds devices on the DIO bus
         * always starts at scode 0 and works its way up.
         */
        if (netboot) {
                for (dd = LIST_FIRST(&dev_data_list); dd != NULL;
                    dd = LIST_NEXT(dd, dd_list)) {
                        if (device_class(dd->dd_dev) == DV_IFNET) {
                                /*
                                 * Found it!
                                 */
                                booted_device = dd->dd_dev;
                                break;
                        }
                }
                return;
        }

        /*
         * Check for HP-IB boots next.
         */
        if (hpibboot) {
                findbootdev_slave(&dev_data_list_hpib, ctlr,
                    slave, punit);
                if (booted_device == NULL)
                        return;

                /*
                 * Sanity check.
                 */
                if ((type == 0 && !device_is_a(booted_device, "ct")) ||
                    (type == 2 && !device_is_a(booted_device, "rd"))) {
                        printf("WARNING: boot device/type mismatch!\n");
                        printf("device = %s, type = %d\n",
                            device_xname(booted_device), type);
                        booted_device = NULL;
                }
                goto out;
        }

        /*
         * Check for SCSI boots last.
         */
        if (scsiboot) {
                findbootdev_slave(&dev_data_list_scsi, ctlr,
                     slave, punit);
                if (booted_device == NULL)
                        return;

                /*
                 * Sanity check.
                 */
                if (type ==  4 &&
                    !device_is_a(booted_device, "sd") &&
                    !device_is_a(booted_device, "cd")) {
                        printf("WARNING: boot device/type mismatch!\n");
                        printf("device = %s, type = %d\n",
                            device_xname(booted_device), type);
                        booted_device = NULL;
                }
                goto out;
        }

        /* Oof! */
        printf("WARNING: UNKNOWN BOOT DEVICE TYPE = %d\n", type);

 out:
        if (booted_device != NULL)
                booted_partition = part;
}

static void
findbootdev_slave(ddlist_t *ddlist, int ctlr, int slave, int punit)
{
        struct dev_data *cdd, *dd;

        /*
         * Find the booted controller.
         */
        for (cdd = LIST_FIRST(ddlist); ctlr != 0 && cdd != NULL;
            cdd = LIST_NEXT(cdd, dd_clist))
                ctlr--;
        if (cdd == NULL) {
                /*
                 * Oof, couldn't find it...
                 */
                return;
        }

        /*
         * Now find the device with the right slave/punit
         * that's a child of the controller.
         */
        for (dd = LIST_FIRST(&dev_data_list); dd != NULL;
            dd = LIST_NEXT(dd, dd_list)) {
                /*
                 * "sd" -> "scsibus" -> "spc"
                 * "rd" -> "hpibbus" -> "fhpib"
                 */
                if (device_parent(device_parent(dd->dd_dev)) != cdd->dd_dev)
                        continue;

                if (dd->dd_slave == slave &&
                    dd->dd_punit == punit) {
                        /*
                         * Found it!
                         */
                        booted_device = dd->dd_dev;
                        break;
                }
        }
}

static void
setbootdev(void)
{
        struct dev_data *cdd, *dd;
        int type, ctlr;

        /*
         * Note our magic numbers for type shared with the BOOTROM:
         *
         *      0 == ct
         *      2 == rd
         *      4 == sd or cd
         *      6 == le
         *
         * All are bdevsw major numbers, except for le and cd.
         * le is just special. cd is treated as sd by the BOOTROM.
         *
         * We can't mount root on a tape, so we ignore those.
         */

        /*
         * Start with a clean slate.
         */
        bootdev = 0;

        /*
         * If the root device is network, we're done
         * early.
         */
        if (device_class(root_device) == DV_IFNET) {
                bootdev = MAKEBOOTDEV(6, 0, 0, 0, 0);
                goto out;
        }

        /*
         * Determine device type.
         */
        if (device_is_a(root_device, "rd"))
                type = 2;
        else if (device_is_a(root_device, "sd"))
                type = 4;
        else if (device_is_a(root_device, "cd"))
                type = 4;       /* not a major, but for MAKEBOOTDEV() */
        else if (device_is_a(root_device, "md"))
                goto out;
        else {
                printf("WARNING: strange root device!\n");
                goto out;
        }

        dd = dev_data_lookup(root_device);

        /*
         * Get parent's info.
         */
        switch (type) {
        case 2: /* rd */
                /*
                 * "rd" -> "hpibbus" -> "fhpib"
                 * "rd" -> "hpibbus" -> "nhpib"
                 */
                for (cdd = LIST_FIRST(&dev_data_list_hpib), ctlr = 0;
                    cdd != NULL; cdd = LIST_NEXT(cdd, dd_clist), ctlr++) {
                        if (cdd->dd_dev ==
                            device_parent(device_parent(root_device))) {
                                /*
                                 * Found it!
                                 */
                                bootdev = MAKEBOOTDEV(type,
                                    ctlr, dd->dd_slave, dd->dd_punit,
                                    DISKPART(rootdev));
                                break;
                        }
                }
                break;
        case 4: /* sd or cd */
                /*
                 * "sd" -> "scsibus" -> "spc"
                 */
                for (cdd = LIST_FIRST(&dev_data_list_scsi), ctlr = 0;
                    cdd != NULL; cdd = LIST_NEXT(cdd, dd_clist), ctlr++) {
                        if (cdd->dd_dev ==
                            device_parent(device_parent(root_device))) {
                                /*
                                 * Found it!
                                 */
                                bootdev = MAKEBOOTDEV(type,
                                    ctlr, dd->dd_slave, dd->dd_punit,
                                    DISKPART(rootdev));
                                break;
                        }
                }
                break;
        }

 out:
        /* Don't need this anymore. */
        for (dd = LIST_FIRST(&dev_data_list); dd != NULL; ) {
                cdd = dd;
                dd = LIST_NEXT(dd, dd_list);
                kmem_free(cdd, sizeof(*cdd));
        }
}

/*
 * Return the dev_data corresponding to the given device.
 */
static struct dev_data *
dev_data_lookup(device_t dev)
{
        struct dev_data *dd;

        for (dd = LIST_FIRST(&dev_data_list); dd != NULL;
            dd = LIST_NEXT(dd, dd_list))
                if (dd->dd_dev == dev)
                        return dd;

        panic("dev_data_lookup");
}

/*
 * Insert a dev_data into the provided list, sorted by select code.
 */
static void
dev_data_insert(struct dev_data *dd, ddlist_t *ddlist)
{
        struct dev_data *de;

#ifdef DIAGNOSTIC
        if (dd->dd_scode < 0 || dd->dd_scode > 255) {
                printf("bogus select code for %s\n", device_xname(dd->dd_dev));
                panic("dev_data_insert");
        }
#endif

        de = LIST_FIRST(ddlist);

        /*
         * Just insert at head if list is empty.
         */
        if (de == NULL) {
                LIST_INSERT_HEAD(ddlist, dd, dd_clist);
                return;
        }

        /*
         * Traverse the list looking for a device who's select code
         * is greater than ours.  When we find it, insert ourselves
         * into the list before it.
         */
        for (; LIST_NEXT(de, dd_clist) != NULL; de = LIST_NEXT(de, dd_clist)) {
                if (de->dd_scode > dd->dd_scode) {
                        LIST_INSERT_BEFORE(de, dd, dd_clist);
                        return;
                }
        }

        /*
         * Our select code is greater than everyone else's.  We go
         * onto the end.
         */
        LIST_INSERT_AFTER(de, dd, dd_clist);
}

/**********************************************************************
 * Code to find and initialize the console
 **********************************************************************/

int conscode;
void *conaddr;

static bool cninit_deferred;
#if NSTI_SGC > 0
static int consslot = -1;
#endif

void
hp300_cninit(void)
{
        struct bus_space_tag tag;
        bus_space_tag_t bst;

        bst = &tag;
        memset(bst, 0, sizeof(struct bus_space_tag));
        bst->bustype = HP300_BUS_SPACE_INTIO;

        /*
         * Look for serial consoles first.
         */
#if NCOM_FRODO > 0
        if (!com_frodo_cnattach(bst, FRODO_BASE + FRODO_APCI_OFFSET(1),
            CONSCODE_INTERNAL))
                return;
#endif
#if NCOM_DIO > 0
        if (!dio_scan(com_dio_cnattach))
                return;
#endif
#if NDCM > 0
        if (!dio_scan(dcmcnattach))
                return;
#endif

#ifndef CONSCODE
        /*
         * Look for internal framebuffers.
         */
#if NDVBOX > 0
        if (!dvboxcnattach(bst, FB_BASE, CONSCODE_INTERNAL))
                goto find_kbd;
#endif
#if NGBOX > 0
        if (!gboxcnattach(bst, FB_BASE, CONSCODE_INTERNAL))
                goto find_kbd;
#endif
#if NRBOX > 0
        if (!rboxcnattach(bst, FB_BASE, CONSCODE_INTERNAL))
                goto find_kbd;
#endif
#if NTOPCAT > 0
        if (!topcatcnattach(bst, FB_BASE, CONSCODE_INTERNAL))
                goto find_kbd;
#endif
#endif  /* CONSCODE */

        /*
         * Look for external framebuffers.
         */
#if NDVBOX > 0
        if (!dio_scan(dvboxcnattach))
                goto find_kbd;
#endif
#if NGBOX > 0
        if (!dio_scan(gboxcnattach))
                goto find_kbd;
#endif
#if NHYPER > 0
        if (!dio_scan(hypercnattach))
                goto find_kbd;
#endif
#if NRBOX > 0
        if (!dio_scan(rboxcnattach))
                goto find_kbd;
#endif
#if NTOPCAT > 0
        if (!dio_scan(topcatcnattach))
                goto find_kbd;
#endif
#if NTVRX > 0
        if (!dio_scan(tvrxcnattach))
                goto find_kbd;
#endif
#if NSTI_DIO > 0
        if (!dio_scan(sti_dio_cnprobe)) {
                cninit_deferred = true;
                goto find_kbd;
        }
#endif
#if NGENDIOFB > 0
        if (!dio_scan(gendiofbcnattach))
                goto find_kbd;
#endif
#if NSTI_SGC > 0
        if (machineid == HP_400 ||
            machineid == HP_425 ||
            machineid == HP_433) {
                struct bus_space_tag sgc_tag;
                bus_space_tag_t sgc_bst;
                u_int slot;

                sgc_bst = &sgc_tag;
                memset(sgc_bst, 0, sizeof(struct bus_space_tag));
                sgc_bst->bustype = HP300_BUS_SPACE_SGC;
                for (slot = 0; slot < SGC_NSLOTS; slot++) {
                        if (sti_sgc_cnprobe(sgc_bst, slot)) {
                                cninit_deferred = true;
                                consslot = slot;
                                goto find_kbd;
                        }
                }
        }
#endif

#if (NDVBOX + NGBOX + NRBOX + NTOPCAT + NDVBOX + NGBOX + NHYPER + NRBOX + \
     NTOPCAT + NTVRX + NGENDIOFB + NSTI_DIO + NSTI_SGC) > 0
find_kbd:
#endif

#if NDNKBD > 0
        dnkbdcnattach(bst, FRODO_BASE + FRODO_APCI_OFFSET(0))
#endif

#if NHILKBD > 0
        /* not yet */
        hilkbdcnattach(bst, HIL_BASE);
#endif
        return;
}

static int
dio_scan(int (*func)(bus_space_tag_t, bus_addr_t, int))
{
#ifndef CONSCODE
        int scode, sctop;

        sctop = DIO_SCMAX(machineid);
        for (scode = 0; scode < sctop; ++scode) {
                if (DIO_INHOLE(scode) || ((scode == 7) && internalhpib))
                        continue;
                if (!dio_scode_probe(scode, func))
                        return 0;
        }
#else
                if (!dio_scode_probe(CONSCODE, func))
                        return 0;
#endif

        return 1;
}

static int
dio_scode_probe(int scode, int (*func)(bus_space_tag_t, bus_addr_t, int))
{
        struct bus_space_tag tag;
        bus_space_tag_t bst;
        void *pa, *va;

        bst = &tag;
        memset(bst, 0, sizeof(struct bus_space_tag));
        bst->bustype = HP300_BUS_SPACE_DIO;
        pa = dio_scodetopa(scode);
        va = iomap(pa, PAGE_SIZE);
        if (va == 0)
                return 1;
        if (badaddr(va)) {
                iounmap(va, PAGE_SIZE);
                return 1;
        }
        iounmap(va, PAGE_SIZE);

        return (*func)(bst, (bus_addr_t)pa, scode);
}

void
hp300_cninit_deferred(void)
{

        if (!cninit_deferred)
                return;

#if NSTI_DIO > 0
        if (machineid == HP_362 ||
            machineid == HP_382) {
                struct bus_space_tag dio_tag;
                bus_space_tag_t dio_bst;

                dio_bst = &dio_tag;
                memset(dio_bst, 0, sizeof(struct bus_space_tag));
                dio_bst->bustype = HP300_BUS_SPACE_DIO;
                sti_dio_cnattach(dio_bst, conscode);
        }
#endif
#if NSTI_SGC > 0
        if (machineid == HP_400 ||
            machineid == HP_425 ||
            machineid == HP_433) {
                struct bus_space_tag sgc_tag;
                bus_space_tag_t sgc_bst;

                sgc_bst = &sgc_tag;
                memset(sgc_bst, 0, sizeof(struct bus_space_tag));
                sgc_bst->bustype = HP300_BUS_SPACE_SGC;
                sti_sgc_cnattach(sgc_bst, consslot);
        }
#endif
}


/**********************************************************************
 * Mapping functions
 **********************************************************************/

/*
 * Initialize the external I/O extent map.
 */
void
iomap_init(void)
{
        int error;

        /* extiobase is initialized by pmap_bootstrap1(). */
        extio_arena = vmem_init(&extio_arena_store,
                                "extio",                /* name */
                                0,                      /* addr */
                                0,                      /* size */
                                PAGE_SIZE,              /* quantum */
                                NULL,                   /* importfn */
                                NULL,                   /* releasefn */
                                NULL,                   /* source */
                                0,                      /* qcache_max */
                                VM_NOSLEEP | VM_PRIVTAGS,
                                IPL_NONE);
        KASSERT(extio_arena != NULL);

        vmem_add_bts(extio_arena, extio_btag_store, EXTIO_BTAG_COUNT);
        error = vmem_add(extio_arena, (vmem_addr_t)extiobase,
            ptoa(EIOMAPSIZE), VM_NOSLEEP);
        KASSERT(error == 0);
}

/*
 * Allocate/deallocate a cache-inhibited range of kernel virtual address
 * space mapping the indicated physical address range [pa - pa+size)
 */
void *
iomap(void *vpa, int size)
{
        paddr_t pa = (paddr_t)vpa;
        vmem_addr_t kva;
        int error;

        KASSERT((pa & PGOFSET) == 0);
        KASSERT((size & PGOFSET) == 0);

        error = vmem_alloc(extio_arena, size, VM_BESTFIT | VM_NOSLEEP, &kva);
        if (error) {
                return NULL;
        }

        vaddr_t va = kva;
        vaddr_t eva = kva + size;
        while (va < eva) {
                pmap_kenter_pa(va, pa, VM_PROT_READ | VM_PROT_WRITE,
                    PMAP_NOCACHE);
                va += PAGE_SIZE;
                pa += PAGE_SIZE;
        }

        return (void *)kva;
}

/*
 * Unmap a previously mapped device.
 */
void
iounmap(void *kva, int size)
{
        vaddr_t va = (vaddr_t)kva;

        KASSERT((va & PGOFSET) == 0);
        KASSERT((size & PGOFSET) == 0);

        KASSERT((uint8_t *)kva >= extiobase);
        KASSERT((uint8_t *)kva < extiobase + ptoa(EIOMAPSIZE));

        pmap_kremove(va, size);

        vmem_free(extio_arena, va, size);
}