OpenBSD stories
miod > software > OpenBSD > stories > Device configuration

When you power on your computer, unless things go wrong, the operating system will boot, discover all the devices in your system, and set up drivers for them.

You might be slightly unsuccessful if your software is older than your hardware and may not support the wireless chip du jour, but usually, everything works.

Have you ever wondered how the operating system is able to do this?


In the old, dark times of early Unix versions, things were easy. Unix did not run on many different systems, these systems did not have many different devices; the kernel could simply come with a driver for everything supported (that is, maybe two storage devices and two serial chips) and be done with it.

As hardware became more diverse, it was obvious this would not work in the long run.

4.1cBSD, a preversion of 4.2BSD which was not yet ready, introduced in 1983 the ability to easily configure the device support in the kernel, with a new utility, /etc/config.

config would read a plaintext configuration file, which could contain a bunch of configurable kernel options (such as its default timezone), as well as a list of supported devices. Devices would be set up in three categories: controllers or masters, to which slave devices (disk or tape) could attach, and simple no-frill devices.

Particular details of the devices would be specified using a few so-called locators to give more precise information about them: for disks, this could be a drive number, and for controllers, the address of its csr (Control and Status Register, usually found at the beginning of the device address space.)

Among the files generated by config, ioconf.c would contain a table of the drivers to attach; each driver of the controller class would need, among other things, to provide a probe routine, which would confirm the device existence at the given address, and force it to generate an interrupt. If the device would not get detected, or if it would fail to interrupt, the driver would not attach.

These requirements were reasonably solid, back then, as:

Here is the 4.1cBSD config manual page, in full (please read at least the ident description):

CONFIG(8)                   System Manager's Manual                  CONFIG(8)

NAME
     config - Build system configuration files

SYNOPSIS
     /etc/config [ -p ] config_file

DESCRIPTION
     Config builds a set of system configuration files from a short file which
     describes the sort of system that is being configured.  It also takes as
     input a file which tells config what files are needed to generate a
     system.  If the -p option is supplied, config will configure a system for
     profiling; c.f.  kgmon(8), gprof(1).

     Config should be run from the conf subdirectory of the system source
     (usually /sys/conf).  Config assumes that there is already a directory
     ../config_file created and it places all its output files in there.  The
     output of config consists of a number files: ioconf.c which contains a
     description of what i/o devices are attached to the system, ubglue.s
     which is a set of interrupt service routines for devices attached to the
     UNIBUS, makefile for building the system and a set of header files which
     contain the number of various devices that will be compiled into the
     system.

     After running config, it is necessary to run "make depend" in the
     directory where the new makefile was created.  Config reminds you of this
     when it completes.

     If you get any other error messages from config, you should fix the
     problems in your configuration file and try again.  If you try to compile
     a system that had configuration errors, you will meet with failure.

CONFIG FILE FORMAT
     In the following descriptions, a number can be a decimal integer, a whole
     octal number or a whole hexadecimal number.  Hex and octal are specified
     to config in the same way they are specified to the C compiler, a number
     starting with "0x" is a hex number and a number starting with just a "0"
     is an octal number.  When specifying the timezone, you may also use
     floating point numbers.

     Comments are specified in a config file with the character "#".  All
     characters from a "#" to the end of a line are ignored.

     Lines beginning with tabs are considered continuations of the previous
     line.

     Lines of the config file can be one of several types.  First there are
     lines which describe general things about your system.  Here is a list of
     the possibilities.

     machine type
          This is system is to run on the machine type specified.  No more
          than one machine type can appear in the config file.  Legal types
          are vax and sun.

     cpu "type"
          This system is to run on the cpu type specified.  More than one cpu
          type can appear in the config file.  Legal types are VAX780, VAX750,
          and VAX730.

     options optlist
          Compile the listed options into the system.  Options in this list
          are seperated by commas.  There is a list of options that you may
          specify in the generic makefile.  A line of the form "options
          FUNNY,HAHA" yields -DFUNNY -DHAHA to the C compiler.  An option may
          be given a value, by following its name with "=" then the value
          enclosed in (double) quotes. None of the standard options use such a
          value.

     timezone number [ dst ]
          Specifies the timezone you are in.  This is measured in the number
          of hours west of GMT you are.  5 is EST, 8 is PST. Negative numbers
          indicate hours east of GMT. If you specify dst, the system will
          operate under daylight savings time.

     ident name
          This system is to be known as name.  This is usually a cute name
          like ERNIE (short for Ernie Co-Vax) or VAXWELL (for Vaxwell Smart).

     maxusers number
          The maximum expected number of simultaneously active user on this
          system is number.  This number is used to size several system data
          structures.

     config device sysname
          Generate a system which runs with its root on device and call it
          sysname.  There may be more than one config specification in a
          config file.

     The second type of line in the config file describes what devices your
     system has and what they are connected to (e.g. I have a DZ-11 on UNIBUS
     Adapter 0).  These lines have the following format.
          dev_type dev_name at con_dev more_info

     Dev_type is either master, tape, disk, controller, device, or
     pseudo-device.  A master is a MASSBUS tape controller.  A controller is a
     disk controller, a UNIBUS tape controller, an mba (MASSBUS) or a uba
     (UNIBUS).  A device is usually something which connects to the uba, like
     a DZ-11 or a DR-11.  Disk and tape should be self-explanatory.  A pseudo-
     device is something which should be conditionally loaded, but is not
     really a device.  Current examples are the bk line discipline, the
     pseudo-tty driver and various network subsystems. (For pseudo-devices,
     more_info may be specified as an integer, that gives the value of the
     symbol defined in the header file created for that device, and is
     generally used to indicate the number of instances of the pseudo-device
     to create. If you load a subsystem you will probably find it convenient
     to enable conditional code using an options specification.

     The dev_name is the name of the device you are specifying.  If it is not
     a pseudo-device, you must give a number afterwards (e.g. dz0, dz1, hp0).

     Con_dev is what the device you are specifying is connected to.  If you
     have a disk on MASSBUS adapter zero then the proper con_dev is mba0.  For
     MASSBUS and UNIBUS adapters, you must give nexus? as the con_dev.

     The more_info field is a sequence of the following:

     csr addr
          Specifies the csr for a device.  Must be given for UNIBUS tape and
          disk controllers and all devices connected to the UNIBUS.  Make
          certain that you put a leading zero on the address so that it will
          be interpreted as an octal number.

     drive number
          For a disk or UNIBUS tape, specifies which drive this is.

     slave number
          For a MASSBUS tape, specifies which tape slave it is.

     flags number
          These flags are passed to the device driver at system initialization
          time.

     vector addr [ addr ]
          For devices which interrupt on the UNIBUS, specifies the interrupt
          service routine.

     The easiest way to understand config files it to look at a working one
     and modify it to suit your system.  Here is a short sample configuration
     file for a system with an RM03, a TU45, a DZ-11 and a DH-11.
          #
          # Sample configuration file
          #
          machine    vax
          cpu        VAX780
          ident      SAMPLE
          hz         60
          timezone   8 dst
          maxusers   24

          config     hp    vmunix
          config     hk    hkvmunix

          controller mba0  at nexus ?
          controller uba0  at nexus ?
          disk       hp0   at mba0 drive 0
          master     ht0   at mba1 drive 0
          tape       tu0   at ht0 slave 0
          pseudo-device    pty                    16
          pseudo-device    bk
          controller hk0   at uba0 csr 0177440    vector rkintr
          disk       rk0   at hk0 drive 0
          disk       rk1   at hk0 drive 1
          device     dh1   at uba0 csr 0160040    vector dhrint dhxint
          device     dz0   at uba0 csr 0160100 flags 0xc0 vector dzrint dzxint

     A ? may be substituted for a number in three places and the system will
     figure out what to fill in for the ? when it boots.  You can put
     question marks on a con_dev (e.g. at mba?), on a drive number (e.g. drive
     ?), or on a slave number (e.g. slave ?) (The latter applies to MASSBUS
     devices only - uba devices don't have slaves).  This allows redundancy as
     a single system can be built which will reboot on different hardware
     configurations.

FILES
     /sys/conf/makefile.vax                       generic makefile for the VAX
     /sys/conf/makefile.sun                       generic makefile for the SUN
     /sys/conf/files list of common files system is built from
     /sys/conf/files.vax   list of VAX specific files
     /sys/conf/files.sun   list of SUN specific files

SEE ALSO
     The SYNOPSIS portion of each device in section 4.

BUGS
     The line numbers reported in error messages are usually off by one.

     Should describe the format of the ``files'' file here; you can probably
     figure it out for yourself in the meantime.

     Configuring multiple MASSBUS drives at spaced intervals generates an
     incorrect ioconf.c file.

4th Berkeley Distribution        1 April 1981                        CONFIG(8)

The configuration file was parsed using a Yacc generated parser, which in turn forced config to use a reasonably simple and non-ambiguous grammar.

For the 4.2BSD release, description of the config file syntax was removed from the manual page and moved to a separate document, ``Building 4.2BSD UNIX System with Config'', which also contained more details on the generated files, and how to add new driver code.

Documentation for the system, in the ``Changes to the Kernel in 4.2BSD'' section, mentioned that

/sys/conf
This directory contains files used in configuring systems.
The format of configuration files has changed slightly;
it is described completely in a new document
``Building 4.2BSD UNIX Systems with Config''.
Several new files exist for use by the config(8)
program, and several old files have had their meaning changed
slightly.

In said document, more naming jokes can be found:

The system identification is a moniker attached to the system, and often
the machine on which the system is to run.  For example, at Berkeley we
have machines named Ernie (Co-VAX), Kim (No-VAX), and so on.  The system
identifier selected is used to create a global C ``#define'' which may be
used to isolate system dependent pieces of code in the kernel.  For
example, Ernie's Varian driver used to be special cased because its
interrupt vectors were wired together.  The code in the driver which
understood how to handle this non-standard hardware configuration was
conditionally compiled in only if the system was for Ernie.

The 4.2BSD config also introduced the specification of the root, swap and dump devices, rather than letting the kernel use the first disk found.


An interesting tidbit of 4.2BSD config is that it introduced, in the syntax of the various files listing the available source files and on which grounds they need to be compiled for a given kernel, the device-driver keyword, which was used, well, for device drivers only, and would use a specific set of compiler flags to compile these files, different than the flags used for the rest of the kernel files. This was used in order to pass the -i option to the compiler (PCC, back then) to force it to consider all memory accesses made through pointer as volatile, as the "volatile" keyword was yet to be invented at that time (1983), and device register layouts were usually declared as a C struct to which a pointer initialized with the particular hardware address (csr value) would point to. Without using such a flag, the optimizer code in the compiler could assume that constructs, such as
    while ((device_pointer->interesting_register & SPECIAL_BIT_TO_CHECK) == 0)
        DELAY(1000);
do not need to perform a register read every time, and would generate code equivalent to
    temporary_variable = device_pointer->interesting_register;
    while ((temporary_variable & SPECIAL_BIT_TO_CHECK) == 0)
        DELAY(1000);
which would cause infinite loops if the bit to check had not been set in the first (and now only) read from the register.

In 4.3BSD-Reno (1990), config moved from /etc to /usr/sbin.


Let's have a closer look at how this worked, using the hp300 port as example.

In the average 4.3BSD-Reno hp300 kernel configuration file, the storage devices will be configured with:

master          hpib0   at scode7
master          hpib1   at scode?
master          hpib2   at scode?
disk            rd0     at hpib? slave 0
disk            rd1     at hpib? slave ?
disk            rd2     at hpib? slave ?
disk            rd3     at hpib? slave ?
tape            ct0     at hpib? slave ?
tape            ct1     at hpib? slave ? flags 1
device          ppi0    at hpib0 slave 5

master          scsi0   at scode?
master          scsi1   at scode?
disk            sd0     at scsi? slave ?
disk            sd1     at scsi? slave ?
disk            sd2     at scsi? slave ?
disk            sd3     at scsi? slave ?
with the associated rules in files.h300:
hpdev/hpib.c            optional hpib
hpdev/nhpib.c           optional hpib
hpdev/fhpib.c           optional hpib
hpdev/rd.c              optional rd
hpdev/ct.c              optional ct
hpdev/ppi.c             optional ppi
hpdev/scsi.c            optional scsi
hpdev/sd.c              optional sd

In 4.4BSD, disks sd4 to sd10 were added, as well as scsi tapes:

tape            st0     at scsi? slave ?
tape            st1     at scsi? slave ?
using this driver:
hp300/dev/st.c                  optional st

(The 4.3BSD-Reno hpdev directory having been moved to hp300/dev in 4.4BSD.)

Based upon these settings, config would produce, among other things, these (sometimes redundant...) declarations in ioconf.c:

#define C (caddr_t)

extern struct driver hpibdriver;
extern struct driver hpibdriver;
extern struct driver hpibdriver;
extern struct driver rddriver;
extern struct driver rddriver;
extern struct driver rddriver;
extern struct driver rddriver;
extern struct driver ctdriver;
extern struct driver ctdriver;
extern struct driver scsidriver;
extern struct driver scsidriver;
extern struct driver sddriver;
extern struct driver sddriver;
extern struct driver sddriver;
extern struct driver sddriver;

struct hp_ctlr hp_cinit[] = {
/*      driver,         unit,   alive,  addr,   flags */
        { &hpibdriver,  0,      0,      C 0x7,  0x0 },
        { &hpibdriver,  1,      0,      C 0x0,  0x0 },
        { &hpibdriver,  2,      0,      C 0x0,  0x0 },
        { &scsidriver,  0,      0,      C 0x0,  0x0 },
        { &scsidriver,  1,      0,      C 0x0,  0x0 },
        0
};

struct hp_device hp_dinit[] = {
/*driver,       cdriver,        unit,   ctlr,   slave,  addr,   dk,     flags*/
{ &&ddriver,    &hpibdriver,    0,      -1,     0,      C 0x0,  1,      0x0 },
{ &rddriver,    &hpibdriver,    1,      -1,     -1,     C 0x0,  1,      0x0 },
{ &rddriver,    &hpibdriver,    2,      -1,     -1,     C 0x0,  1,      0x0 },
{ &rddriver,    &hpibdriver,    3,      -1,     -1,     C 0x0,  1,      0x0 },
{ &ctdriver,    &hpibdriver,    0,      -1,     -1,     C 0x0,  0,      0x0 },
{ &ctdriver,    &hpibdriver,    1,      -1,     -1,     C 0x0,  0,      0x1 },
{ &sddriver,    &scsidriver,    0,      -1,     -1,     C 0x0,  1,      0x0 },
{ &sddriver,    &scsidriver,    1,      -1,     -1,     C 0x0,  1,      0x0 },
{ &sddriver,    &scsidriver,    2,      -1,     -1,     C 0x0,  1,      0x0 },
{ &sddriver,    &scsidriver,    3,      -1,     -1,     C 0x0,  1,      0x0 },
};

(The C macro is simply here to make the table easier to read. It is simply a cast operation of the value found after it, to the caddr_t type, which is simply a typedef for char *, a pointer. That typedef was intended to describe generic untyped (void *) pointer, as not all C compilers did support the void * syntax yet.)

The layout of the hp_ctrl and hp_device structures would be defined in hpdev/device.h in 4.3BSD-Reno, later hp/dev/device.h in 4.4BSD:

struct driver {
        int     (*d_init)();
        char    *d_name;
        int     (*d_start)();
        int     (*d_go)();
        int     (*d_intr)();
        int     (*d_done)();
};

struct hp_ctlr {
        struct driver   *hp_driver;
        int             hp_unit;
        int             hp_alive;
        char            *hp_addr;
        int             hp_flags;
        int             hp_ipl;
};

struct hp_device {
        struct driver   *hp_driver;
        struct driver   *hp_cdriver;
        int             hp_unit;
        int             hp_ctlr;
        int             hp_slave;
        char            *hp_addr;
        int             hp_dk;
        int             hp_flags;
        int             hp_alive;
        int             hp_ipl;
};
[...]
struct hp_hw {
        char    *hw_addr;       /* physical address of registers */
        short   hw_sc;          /* select code (if applicable) */
        short   hw_type;        /* type (defined below) */
        short   hw_id;          /* HW returned id */
        short   hw_id2;         /* secondary HW id (displays) */
        char    *hw_name;       /* HP product name */
};

#define MAX_CTLR        16      /* Totally arbitrary */
#define MAXSLAVES       8       /* Currently the HPIB limit */

#define WILD_CARD_CTLR  0

/* A controller is a card which can have one or more slaves attached */
#define CONTROLLER      0x10
#define HPIB            0x16
#define SCSI            0x17
[...]
#ifdef KERNEL
extern struct hp_ctlr   hp_cinit[];
extern struct hp_device hp_dinit[];
extern struct hp_hw     sc_table[];
#endif

At this point, we can see that, thanks to config's work, we have a table of controllers, hp_cinit, which will be used to setup HP-IB and SCSI drivers, and a table of devices, hp_dinit, which will be used to set up the real devices: disks and tapes. But there is also the declaration of a sc_table array, which is not populated by config. This is the last piece of the puzzle.

Among the very first actions the kernel would perform, once it has performed enough work to be able to run C code (the early initialization code being written in assembly language), is the startup function in hp300/machdep.c.

/*
 * Machine-dependent startup code
 */
startup(firstaddr)
        int firstaddr;
{
[...]
        /*
         * Find what hardware is attached to this machine.
         */
        find_devs();
[...]
}

As its name suggests, the role of find_devs is to find the actual controllers in the system. Its code can be found in hp300/autoconf.c:

struct  hp_hw sc_table[MAX_CTLR];

find_devs()
{
        short sc;
        u_char *id_reg;
        register int addr;
        register struct hp_hw *hw;

        hw = sc_table;
        for (sc = -1; sc < 32; sc++) {
[...]
                /*
                 * Probe all select codes + internal display addr
                 */
                if (sc == -1)
                        addr = IOV(GRFIADDR);
                else
                        addr = sctoaddr(sc);
                if (badaddr((caddr_t)addr))
                        continue;

                id_reg = (u_char *) addr;
                hw->hw_id = id_reg[1] & 0xff;
                hw->hw_sc = sc;
                hw->hw_addr = (char *) addr;
[...]
                /*
                 * XXX: the following could be in a big static table
                 */
                switch (hw->hw_id) {
[...]
                case 7:
                case 39:
                case 71:
                case 103:
                        hw->hw_name = "98265A";
                        hw->hw_type = SCSI;
                        break;
                case 8:
                        hw->hw_name = "98625B";
                        hw->hw_type = HPIB;
                        break;
[...]
                }
                hw++;
        }
}

On hp300 hardware, devices (either onboard or as expansion cards) conform to HP's own DIO (Device I/O) specification. The DIO bus is enumerable, and can contain up to 32 different devices, each having a different number. These numbers are known as their select codes, or sc for short. So this holy diver routine simply performs a loop over the 32 select code values, computes the address a card configured with that select code would respond to, using the sctoaddr (select code to address) routine, check for a device answering bus cycles at this address using badaddr (bad address test), and if there is a card, decide what it is based on its identification byte (id_reg.) The special value of -1 in the loop is used to probe for an internal frame buffer, which address is outside the DIO address space.

(32 select codes turned out not to be enough eventually, so in order to prevent its customers from being caught in the middle, HP designed a superset of the DIO bus, called the DIO-II bus, with three more bits of select codes, spanning select codes values from 33 to 255; support for these DIO-II devices was added in 4.4BSD. I opted to show the 4.3BSD-Reno code here as it is a bit simpler.)

As devices would be found and identified, the sc_table would eventually get populated with, for each device, its name, type, select code, physical address and DIO id. (Note that there is absolutely no check that the hw++ instruction, which advances to the next sc_table entry, does not go off-bounds. The MAX_CTLR value of 16 was expected to hopefully be enough...)

Later in the boot process, the configure routine in hp300/autoconf.c would walk the sc_table in order to invoke the appropriate driver initialization code:

/*
 * Determine mass storage and memory configuration for a machine.
 */
configure()
{
        register struct hp_hw *hw;
        int found;

[...]
        /*
         * Look over each hardware device actually found and attempt
         * to match it with an ioconf.c table entry.
         */
        for (hw = sc_table; hw->hw_type; hw++) {
                if (hw->hw_type & CONTROLLER)
                        found = find_controller(hw);
                else
                        found = find_device(hw);
        }
[...]

The value of SCSI and HPIB assigned in find_devs have been chosen so as to have the CONTROLLER bit set, so they would be handled by find_controller.

find_controller(hw)
        register struct hp_hw *hw;
{
        register struct hp_ctlr *hc;
        struct hp_ctlr *match_c;
[...]
        match_c = NULL;
        for (hc = hp_cinit; hc->hp_driver; hc++) {
                if (hc->hp_alive)
                        continue;
                /*
                 * Make sure we are looking at the right
                 * controller type.
                 */
                if (!same_hw_ctlr(hw, hc))
                        continue;
                /*
                 * Exact match; all done
                 */
                if ((int)hc->hp_addr == sc) {
                        match_c = hc;
                        break;
                }
                /*
                 * Wildcard; possible match so remember first instance
                 * but continue looking for exact match.
                 */
                if ((int)hc->hp_addr == WILD_CARD_CTLR && match_c == NULL)
                        match_c = hc;
        }
        /*
         * Didn't find an ioconf entry for this piece of hardware,
         * just ignore it.
         */
        if (match_c == NULL)
                return(0);
        /*
         * Found a match, attempt to initialize and configure all attached
         * slaves.  Note, we can still fail if HW won't initialize.
         */
        hc = match_c;
        oaddr = hc->hp_addr;
        hc->hp_addr = hw->hw_addr;
        if ((*hc->hp_driver->d_init)(hc)) {
                hc->hp_alive = 1;
                printf("%s%d", hc->hp_driver->d_name, hc->hp_unit);
                sc = addrtosc((u_int)hc->hp_addr);
                if (sc < 256)
                        printf(" at sc%d,", sc);
                else
                        printf(" csr 0x%x,", sc);
                printf(" ipl %d", hc->hp_ipl);
                if (hc->hp_flags)
                        printf(" flags 0x%x", hc->hp_flags);
                printf("\n");
                find_slaves(hc);
        } else
                hc->hp_addr = oaddr;
        return(1);
}

The first part of this routine loops over hp_cinit, attempting to match the entry against the given controller information. Remember we had these contents for hp_cinit:

struct hp_ctlr hp_cinit[] = {
/*      driver,         unit,   alive,  addr,   flags */
        { &hpibdriver,  0,      0,      C 0x7,  0x0 },
        { &hpibdriver,  1,      0,      C 0x0,  0x0 },
        { &hpibdriver,  2,      0,      C 0x0,  0x0 },
        { &scsidriver,  0,      0,      C 0x0,  0x0 },
        { &scsidriver,  1,      0,      C 0x0,  0x0 },
        0
};
The same_hw_ctlr macro makes sure, by accessing the d_name field of the hp_driver pointer found in the hp_driver field of each hp_cinit element, that only HPIB controllers will match with &hpibdriver, and SCSI controllers with &scsidriver. The hp_addr field being nonzero in the first element also restricts it to match against a controller having that particular select code.

Once a match is found, with match_c having a non-NULL value, this entry gets activated, with the driver's d_init entry point being invoked. If it succeeds, the alive field is set, in order to prevent this driver to be tried again.

Then, the find_slaves routine gets invoked in order to attach the subdevice.

(find_device uses a similar logic to find_controller, but operates on the devices list, hp_dinit; once a device is found and its associated driver successfully initializes, however, find_slaves will not get invoked in this case.)

The find_slaves routine is extremely simple...

find_slaves(hc)
        struct hp_ctlr *hc;
{
        /*
         * The SCSI bus is structured very much like the HP-IB
         * except that the host adaptor is slave 7 so we only want
         * to look at the first 6 slaves.
         */
        if (dr_type(hc->hp_driver, "hpib"))
                find_busslaves(hc, MAXSLAVES);
        else if (dr_type(hc->hp_driver, "scsi"))
                find_busslaves(hc, MAXSLAVES-1);
}

...because the real work is done in the find_busslaves routine. The comment block preceding that routine says:

/*
 * Search each BUS controller found for slaves attached to it.
 * The bad news is that we don't know how to uniquely identify all slaves
 * (e.g. PPI devices on HP-IB).  The good news is that we can at least
 * differentiate those from slaves we can identify.  At worst (a totally
 * wildcarded entry) this will cause us to locate such a slave at the first
 * unused position instead of where it really is.  To save grief, non-
 * identifing devices should always be fully qualified.
 */

What find_busslaves roughly did was, for every possible slave value (i.e. the SCSI device ID or the HP-IB address), check for a device responding to this address, and identify it:

find_busslaves(hc, maxslaves)
        register struct hp_ctlr *hc;
        int maxslaves;
{
        register int s;
        register struct hp_device *hd;
        struct hp_device *match_s;
[...]
        int rescan;

        for (s = 0; s < maxslaves; s++) {
                rescan = 1;
                match_s = NULL;
                for (hd = hp_dinit; hd->hp_driver; hd++) {
[... device selection logic omitted ...]
                }
                /*
                 * Found a match.  We need to set hp_ctlr/hp_slave properly
                 * for the init routines but we also need to remember all
                 * the old values in case this doesn't pan out.
                 */
                if (match_s) {
                        hd = match_s;
[...]
                        if ((*hd->hp_driver->d_init)(hd)) {
                                printf("%s%d at %s%d, slave %d",
                                       hd->hp_driver->d_name, hd->hp_unit,
                                       hc->hp_driver->d_name, hd->hp_ctlr,
                                       hd->hp_slave);
                                if (hd->hp_flags)
                                        printf(" flags 0x%x", hd->hp_flags);
                                printf("\n");
                                hd->hp_alive = 1;
                                if (hd->hp_dk && dkn < DK_NDRIVE)
                                        hd->hp_dk = dkn++;
                                else
                                        hd->hp_dk = -1;
                                rescan = 1;
                        }
[...]
                }
[...]
        }
}

HP-IB busses really support 31 devices (5 address bits), but on hp300 systems, apparently only up to 8 devices (3 address bits) were allowed. However, up to 16 subdevices (called physical units) could be connected at each of the 8 device addresses.

There is similar scheme for SCSI devices with the LUN (Logical Unit Number), allowing multiple devices to answer to the same address. Usage of SCSI LUN is quite rare, but it makes sense for example for a tape robot, on which every individual tape would be on its own LUN, allowing all the tapes to be operated simultaneously. However, not all SCSI controllers support more than one LUN per target, as this was not part of the first SCSI specifications.

For HP-IB storage devices, the subdevice functionality was only used by a few specific devices: some "disk plus tape drive all in one enclosure" devices, as well as some dual-floppy disk drives.

If you look again at the way tape drives were configured:

tape            ct0     at hpib? slave ?
tape            ct1     at hpib? slave ? flags 1
you may have noticed that the second device entry, ct1, mentions flags 1.

This is the magic allowing the second tape of a dual-tape controller to attach. And by magic, I mean "horrible kluge".

The use of the flags locator here works with a special logic in hp300/autoconf.c find_busslaves(), which I had omitted in the previous code snippet. At the end of the inner for loop (the loop over the hp_dinit devices table), this logic was documented with:

/*
 * XXX: This should be handled better.
 * Re-scan a slave.  There are two reasons to do this.
 * 1. It is possible to have both a tape and disk
 *    (e.g. 7946) or two disks (e.g. 9122) at the
 *    same slave address.  Here we need to rescan
 *    looking only at entries with a different
 *    physical unit number (hp_flags).
 * 2. It is possible that an init failed because the
 *    slave was there but of the wrong type.  In this
 *    case it may still be possible to match the slave
 *    to another ioconf entry of a different type.
 *    Here we need to rescan looking only at entries
 *    of different types.
 * In both cases we avoid looking at undesirable
 * ioconf entries of the same type by setting their
 * alive fields to -1.
 */

Therefore, a HP-IB device driver would attach, it would set a rescan variable, which in turn would cause the same device id to be probed a second time, after temporarily "hiding" the config stanzas corresponding to the newly attached device. This would allow config stanzas with "flags 1" to get their chance at matching a device with a physical unit number matching the value of "flags".

The complete operation of setting up device drivers in 4.3BSD-Reno was thus:


On the vax, things were sligthly different, due to hardware differences; but the logic was similar: probe top-level controllers, then configure them and attach subdevices as necessary. There was, however, no intermediate hardware table (sc_table) being built.

Because of these differences, config had per-machine code to produce the ioconf.c file. Some locators were also specific to a particular machine, such as the scode locator specifying select codes on hp300. The rules found in the generated Makefile could also differ between platforms, as shown in this snippet of mkmakefile.c in the 4.3BSD-Reno config source code.

[...]
        switch (ftp->f_type) {

        case NORMAL:
                switch (machine) {

                case MACHINE_VAX:
                case MACHINE_TAHOE:
                        fprintf(f, "\t${CC} -c -S ${COPTS} %s../%sc\n",
                                extras, np);
                        fprintf(f, "\t${C2} %ss | ${INLINE} | ${AS} -o %so\n",
                            tp, tp);
                        fprintf(f, "\trm -f %ss\n\n", tp);
                        break;

                case MACHINE_HP300:
                        fprintf(f, "\t${CC} -c ${CFLAGS} %s../%sc\n\n",
                                extras, np);
                        break;
                }
                break;

        case DRIVER:
                switch (machine) {

                case MACHINE_VAX:
                case MACHINE_TAHOE:
                        fprintf(f, "\t${CC} -c -S ${COPTS} %s../%sc\n",
                                extras, np);
                        fprintf(f,"\t${C2} -i %ss | ${INLINE} | ${AS} -o %so\n",
                            tp, tp);
                        fprintf(f, "\trm -f %ss\n\n", tp);
                        break;

                case MACHINE_HP300:
                        fprintf(f, "\t${CC} -c ${CFLAGS} %s../%sc\n\n",
                                extras, np);
                        break;
                }
                break;
[...]

(This snippet also shows the addition of the -i option to the second compiler pass (${C2}) for files of the device-driver class, on vax and tahoe platforms, while hp300, which uses GNU gcc as its C compiler and has the appropriate volatile keywords in its drivers, handles these files exactly the same way as normal files.)


In 4.4BSD, a new kernel configuration mechanism was introduced, in order to provide better flexibility, such as arbitrary locator names, and the ability to attach a given driver to different parent devices. In this new model, the kernel creates a device tree, starting at a fictitious root node, to which a mainbus0 attaches, to which on-board devices and/or secondary busses attach in turn.

This hierarchy model was pioneered by Sun Microsystems, when introducing the OpenPROM (now known as OpenFirmware or IEEE-1275), and it should be no surprise that the new config mechanism was created in order to support the BSD port to SPARC hardware.

This tree setup also removed the need to separate devices between controllers, devices and slaves (subdevices), and allowed the logic responsible for walking the config-generated data in order to decide which driver to attach and how, to be moved to the machine-independent part of the kernel, reducing the amount of similar, but not exactly the same, code being almost duplicated in the machine-dependent part of every architecture supported by 4.4BSD.

Another consequence of this was that config no longer needed to have machine-specific code. All the platforms would behave the same way, and ioconf.c would contain the same data structures regardless of the platform (their contents, of course, would depend on the configuration files having been processed.)

The complete operation of setting up device drivers in the kernel would become:

The existing config code was however kept; a new binary, simply called config.new was to be used for platforms make use of the new code.

The manual page of config was updated to mention that

This is the old version of the config program.
It understands the old autoconfiguration scheme
used on the HP300, DECstation, and derivative platforms.
The new version of config is used with the
SPARC and i386 platforms.
while the manual page of config.new would describe itself as
This is the new version of the config program.
It understands the more modern autoconfiguration scheme
used on the SPARC and i386 platforms.
The old version of config is still used with the
HP300, DECstation, and derivative platforms.

Only one config binary was to be found on the system, though; depending on which platform one would build the system on, either one of config and config.new would be built and installed as /usr/sbin/config.

Despite what was written in the manual pages, in 4.4BSD, only sparc was using the new configuration code, and the i386 code was still using the old configuration style.


When the NetBSD project started, one of the short-term goals was to convert the platforms still using the old config machinery (hp300, i386, luna68k, pmax, vax, although the last three were not yet present in NetBSD) to the new config, in order to ease maintainance and driver code sharing.

In particular, new m68k-based ports (amiga, mac68k, sun3, later atari...) were made to use the new config machinery quickly, if not from the beginning.

In april 1995, in the NetBSD code base, only the hp300 and pmax ports were left using the old config. NetBSD/pmax would eventually switch to the new config in december.

Despite a fair share amount of activity, the hp300 port was not really maintained, and Charles Hannum, who had done most of the work of keeping it working in NetBSD, did not want to assume the portmaster role. To fill his spot, Jason Thorpe got appointed hp300 portmaster on august 1995.

The least one can say is that hp300 work kept him busy. He had to work, among other things:

Switching to new config was still on his radar, as the first step of that work, involving changes to the console code, were announced near the end of december 1995, with more details in february 1996.

After some debugging, these changes would get commited a few days later.

Completely rework how the console is probed.  Console probing no longer
requires pre-autoconfigured devices.  Fix up some prototypes.  Part of the
long journey towards new config.  (GETTING THERE!)

(On the same day, there was an update to telnetd. Remember ssh was only a few months old, back then!)

The new config work is mentioned again in a detailed status report in late august:

Date: 08/15/1996 11:44:47
From: Jason Thorpe
To: port-hp300
Subject: Status report

Hi folks...

Yah, you haven't been hearing from me for a while... "swamped" is the word
I would use to describe my schedule recently.  There's a lot of
back-email I still haven't gotten a chance to reply to, and a lot of
pre-release foo I haven't gotten a chance to do yet.  Be patient with me,
folks... :-)

As a result of extreme busy-ness, I'm taking a short (5-day) vacation
starting today.  I'll have e-mail access, but I plan on spending a good
portion of my time away from a CRT :-)

Here's what I plan to get to when I return (late Tuesday night):

        - Finish the final check on the NetBSD/hp300 1.2 release, and
          put the last pre-release snapshot up for FTP.

[...]
        - Get cranking on hp300 development again, including:
[...]
                * Get working on new config again.  This was stalled
                  due to time constraints and a pesky stray pointer bug.
                  Having the programmers manual for the Fujitsu SCSI
                  chip is a big help; the Sharp x68k system uses the
                  same SCSI chip, so an MI driver (a'la the MI 5380 driver)
                  will be forthcoming.

Anyhow, just wanted you folks to know that I'm still around.  Keep your
ears open for the snapshot announcement middle-next week.

Ciao.
[...]

The hp300 binaries for the NetBSD 1.2 release became available late september, which allowed Thorpe to resume development work, although he had to chase and fix an embarrassing bug first.

A first milestone of the conversion to the new config was commited on december 17th.

Snapshot of new config for NetBSD/hp300.  This isn't quite finished yet.
We're about 75% there.  SCSI and HP-IB are not yet supported in a new
config kernel; some autoconfiguration hackery has to be done there, yet.
These changes are enough to network boot a diskless kernel.

New config glue is enabled with the "NEWCONFIG" kernel option.  If that
option is not present, an old config kernel will be built.  Any kernel
configured with config(8) will automatically pick up the NEWCONFIG
option from std.hp300.

Thorpe mentioned it on the mailinglist as well:

Date: 12/17/1996 01:06:56
From: Jason Thorpe
To: port-hp300
Subject: new config snapshot in tree

I've just committed my work-to-date on converting the hp300 port
to new config(8) to the tree.  If you are running diskless machines,
and want to try it out, see the NEWCONFIG kernel config file for
details on how to configure your kernel.

You'll note that it's possible in new config kernels to leave out
framebuffer drivers that you don't need.  I imagine that all
current NetBSD/hp300 systems will either use the "topcat" or "hyper"
framebuffer drivers.  If you comment the other ones out, you can
make your kernel smaller!  (Wow, that hasn't happened in a _long time_ :-)

Anyhow, HP-IB and SCSI aren't yet supported in a new config kernel.
I want to hack that in soon, but I'm not making any promises.

Anyhow, play around with it, and if you have any problems with new
or old config kernels, let me know.
[...]

After a few more days of work on the disk drivers, the work was finally complete, and got commited on january 30th.

Garbage-collect old-style autoconfiguration code.  Adopt [sic] boot device
detection code to new-style autoconfiguration.

The good news could then be announced to the public:

Date: 01/30/1997 03:04:05
From: Jason Thorpe
To: port-hp300
Subject: README: new config!

[ Forgive me... it's late, I'm tired, and have to get up in just a few
  hours; this may be less coherent than it could be... ]

I'm happy to announce that I've completed the conversion to new-style
autoconfiguration.

What this means to you:

        - You MUST update your kernel configuration files!  See the
          BASALT, DISKLESS, and GENERIC configs for examples.

        - You PROBABLY want to rebuild your kernels from scratch.
          Quite a lot of code has changed.

I've tested this pretty thoroughly on my hp300s at home.  However,
if you encounter any problems, please let me know ASAP (please file
a bug report with send-pr!).

As always, post questions here, and I'll try to answer them as promptly
as I can.
[...]

Moments later, the code for the old config binary was removed from the NetBSD tree.


And all is well that ends well.

Well... not quite.

When Thorpe changed the hp300 code to use the new config, the storage device configuration became:

mainbus0        at root         # root "bus"
dio0            at mainbus0     # DIO/DIO-II bus

nhpib0          at dio? scode 7         # slow internal HP-IB
nhpib*          at dio? scode ?

fhpib*          at dio? scode ?         # `fast' HP-IB

hpibbus0        at nhpib0
hpibbus*        at nhpib?
hpibbus*        at fhpib?

rd*             at hpibbus? slave ? punit ?     # HP-IB disks
ct*             at hpibbus? slave ? punit ?     # HP-IB cartridge tapes
mt*             at hpibbus? slave ? punit ?     # HP-IB 9-track tape

oscsi*          at dio? scode ?         # Old HP SCSI

sd*             at oscsi? target ? lun ?        # SCSI disks
st*             at oscsi? target ? lun ?        # SCSI tapes
ac*             at oscsi? target ? lun ?        # SCSI changers

The first visible change, compared against the old config scheme, is that the DIO bus layer is made visible; the newly introduced dio driver will take care of enumerating the hardware by probing all the available select codes.

Similarly, the HP-IB controller code, which was already split in three files (common code, internal and fast variants) has been split into two drivers (internal and fast) to which the common layer attaches in the form of a hpibbus. This new driver will also take care of searching for its subdevices by probing the available HP-IB device addresses.

The existing disk drivers, now the leaves of the device tree, remain mostly unchanged.


In dev/hpib.c, Thorpe added that description:

/*
 * HP-IB is essentially an IEEE 488 bus, with an HP command
 * set (CS/80 on `newer' devices, Amigo on before-you-were-born
 * devices) thrown on top.  Devices that respond to CS/80 (and
 * probably Amigo, too) are tagged with a 16-bit ID.
 *
 * HP-IB has a 2-level addressing scheme; slave, the analog
 * of a SCSI ID, and punit, the analog of a SCSI LUN.  Unforunately,
 * IDs are on a per-slave basis; punits are often used for disk
 * drives that have an accompanying tape drive on the second punit.
 *
 * In addition, not all HP-IB devices speak CS/80 or Amigo.
 * Examples of such devices are HP-IB plotters, which simply
 * take raw plotter commands over 488.  These devices do not
 * have ID tags, and often the host cannot even tell if such
 * a device is attached to the system!
 */

The new code would pass the actual HP-IB device driver the following information:

/*
 * Attach an HP-IB device to an HP-IB bus.
 */
struct hpibbus_attach_args {
        u_int16_t ha_id;                /* device id */
        int     ha_slave;               /* HP-IB bus slave */
        int     ha_punit;               /* physical unit on slave */
};

...and would make the hpibbus layer look for its own subdevices this way:

void
hpibbus_attach_children(sc)
        struct hpibbus_softc *sc;
{
        struct hpibbus_attach_args ha;
        int slave;

        for (slave = 0; slave < 8; slave++) {
                /*
                 * Get the ID tag for the device, if any.
                 * Plotters won't identify themselves, and
                 * get the same value as non-existent devices.
                 */
                ha.ha_id = hpibid(sc->sc_dev.dv_unit, slave);

                ha.ha_slave = slave;    /* not to be modified by children */
                ha.ha_punit = 0;        /* children modify this */

                /*
                 * Search though all configured children for this bus.
                 */
                (void)config_search(hpibbussearch, &sc->sc_dev, &ha);
        }
}

config_search is a machine-independent function introduced with the new config code, and lives in kern/subr_autoconf.c. It will take care of attempting to find whether a driver can attach to a given parent (here, &sc->sc_dev, which is a struct device *), with the provided attachment details (&ha, which is a struct hpibbus_attach_args * shown above), with the help of a filtering function (here, hpibbussearch) to further refine the device matching rules.

int
hpibbussearch(parent, cf, aux)
        struct device *parent;
        struct cfdata *cf;
        void *aux;
{
        struct hpibbus_softc *sc = (struct hpibbus_softc *)parent;
        struct hpibbus_attach_args *ha = aux;

        /* Make sure this is in a consistent state. */
        ha->ha_punit = 0;

        if ((*cf->cf_attach->ca_match)(parent, cf, ha) > 0) {
                /*
                 * The device probe has succeeded, and filled in
                 * the punit information.  Make sure the configuration
                 * allows for this slave/punit combination.
                 */
                if (cf->hpibbuscf_slave != HPIBBUSCF_SLAVE_DEFAULT &&
                    cf->hpibbuscf_slave != ha->ha_slave)
                        goto out;
                if (cf->hpibbuscf_punit != HPIBBUSCF_PUNIT_DEFAULT &&
                    cf->hpibbuscf_punit != ha->ha_punit)
                        goto out;

                /*
                 * Allocate the device's address from the bus's
                 * resource map.
                 */
                if (hpibbus_alloc(sc, ha->ha_slave, ha->ha_punit))
                        goto out;

                /*
                 * This device is allowed; attach it.
                 */
                config_attach(parent, cf, ha, hpibbusprint);
        }
 out:
        return (0);
}

In this case, hpibbussearch will invoke the candidate driver's matching function, to let it confirm it can work with the given attachment arguments. If it does, config_attach, another machine-independent function of the new config machinery, will take care of performing the actual work (allocating and setting up a struct device for the new device, invoking the driver's attach function, report the new device in the kernel messages, etc.)

In the case of an HP-IB disk driver, the ca_match routine would be:

int
rdmatch(parent, match, aux)
        struct device *parent;
        struct cfdata *match;
        void *aux;
{
        struct hpibbus_attach_args *ha = aux;

        /*
         * Set punit if operator specified one in the kernel
         * configuration file.
         */
        if (match->hpibbuscf_punit != HPIBBUSCF_PUNIT_DEFAULT &&
            match->hpibbuscf_punit < HPIB_NPUNITS)
                ha->ha_punit = match->hpibbuscf_punit;

        if (rdident(parent, NULL, ha) == 0) {
                /*
                 * XXX Some aging HP-IB drives are slow to
                 * XXX respond; give them a chance to catch
                 * XXX up and probe them again.
                 */
                delay(10000);
                ha->ha_id = hpibid(parent->dv_unit, ha->ha_slave);
                return (rdident(parent, NULL, ha));
        }
        return (1);
}

The interesting part of this logic is that, in hpibbussearch, the ha_punit field of the hpibbus_attach_args struct gets initialized to zero, and the rdmatch probe routine for a disk is allowed to override this value, if it is invoked on behalf of a config stanza specifying a non-default value.

In other words, specifying

rd*             at hpibbus? slave 3 punit 2
would match a disk answering to the physical unit 2 of address 3, but the default line
rd*             at hpibbus? slave ? punit ?
which was intended to match a disk anywhere, would cause ha_punit to remain unchanged, as if there have been an implicit punit 0 locator. It would thus behave as:
rd*             at hpibbus? slave ? punit 0

This bug went unnoticed for 8 years, because hp300 systems were quickly becoming a thing of the past, and because, as mentioned already, HP-IB devices using more than one physical unit were also uncommon.

But I'm not here to blame Jason Thorpe. Bugs happen, then hopefully eventually get noticed, then get fixed. The interesting part of this story (at least to me) is how the bug was found, and how it was fixed.


My first HP-IB device was a tiny 77MB disk connected to a small HP 9000/318 system:

[ using 120200 bytes of bsd a.out symbol table ]
Copyright (c) 1982, 1986, 1989, 1991, 1993
        The Regents of the University of California.  All rights reserved.
Copyright (c) 1995-2001 OpenBSD. All rights reserved.  http://www.OpenBSD.org

OpenBSD 2.8-current (SCOUMOUNE) #0: Sat Mar  3 22:00:59 GMT 2001
    root@epi:/src/sys/arch/hp300/compile/SCOUMOUNE
HP 9000/318/319/330 (16MHz MC68020 CPU, MC68851 MMU, 16MHz MC68881 FPU)
real mem  = 4182016 (4084K)
avail mem = 2097152 (2048K)
using 102 buffers containing 417792 bytes (408K) of memory
Parity detection enabled
mainbus0 (root)
intio0 at mainbus0
dio0 at mainbus0: 98620C, 2 channels, 32 bit DMA
nhpib0 at dio0 scode 7 ipl 3: internal HP-IB
hpibbus0 at nhpib0
hd0 at hpibbus0 slave 0 punit 0: 7957A
hd0: 1036 cylinders, 7 heads, 159544 blocks, 512 bytes/block
dca0 at dio0 scode 9 ipl 5: console, no fifo
le0 at dio0 scode 21 ipl 5: address 08:00:09:02:c5:5c
le0: 8 receive buffers, 2 transmit buffers
interrupt levels: bio = 3, net = 5, tty = 5
boot device: hd0
rootdev=0x200 rrootdev=0x900 rawdev=0x902
(due to the introduction of the rd driver for the internal ramdisk found on bsd.rd installation media, the HP-IB disk driver had been renamed from rd to hd on OpenBSD. Also note the use of a custom kernel, rather than GENERIC. When your machine only has four megabytes of memory, with no memory expansion possible, every byte in the kernel is worth saving.)

A 77MB disk turned out to be too small for a complete installation of OpenBSD. Installing the base system only (no X11, no compilers...) would already take about 70MB, and would slowly grow over time. That would leave almost no room for swap and /etc and /var files.

I tried to get my hands on a larger HP-IB disk. But all the HP-IB devices I could gather across the years were a 9-track tape drive, for which I had no matching tape anyway, and a 9122C single floppy.

At some point in november 2005, I scratched an itch, something I had wanted to do for years: report a human-readable disk size. Instead of:

hd0: 1036 cylinders, 7 heads, 159544 blocks, 512 bytes/block
I wanted to report, similarly to the way SCSI disks would report themselves:
hd0: 77MB, 1036 cyl, 7 head, 22 sec, 512 bytes/sec, 159544 sec total

That was a trivial change.

Print HP-IB disk geometry the same way we print SCSI disk geometries
(especially, with the size in MB.)

I was curious what size would be reported for the floppy disk drive. So, for the first time, I booted an hp300 system with the HP-IB floppy drive connected, and it would report an acceptable rounded-down size of 1MB.

Since I was making changes to the HP-IB code, it was the perfect time for an overdue cleanup of that code. Looking at the attachment code for hpibbus subdevices, I noticed that the use of config_attach within a config_search callback would hide devices which would exist, but for which no driver would attach. In other words, if you were to recompile a kernel without the hd driver for HP-IB drives, you would no longer get any output for them (no "something at hpibbus0 slave N not configured" messages), and thus no way to realize that your kernel is lacking a driver for that device.

This was another simple change, as the new config code provides a config_found_sm routine (sm standing for submatch), which wraps config_search but makes sure that, when no driver attaches, a "not configured" message will get output.

Use config_found_sm() in the probe machinery. This is simpler and allows us
to report HP-IB devices found for which no driver attached.

At this point, while still tinkering with the device attachment logic, I realized that we were never attempting to probe for devices with non-zero physical unit values, due to the bug introduced while switching to the new config code.

Since I don't know of any way to know whether a given HP-IB device will answer to multiple physical unit numbers, and if so, how many of them, the only safe way to be sure to not risk missing any was to simply probe the complete physical unit space, i.e. try all punit values unless there was a specific value for the punit locator.

This was done in this commit, which also allowed some dead code to be removed.

Overhaul the way HP-IB devices are probed. We will now do an exhaustive
probe of the (slave, punit) tuple space, since this is the only way we
can get a dual disk or dual tape enclosure to attach two devices of the
same kind.

This allows us to get rid of the hpibbus resource map and related
functions.

But then, something unexpected happened. Testing the floppy disk drive again, it would now attach twice, to punit 0 and 1. And, unsurprisingly, the "phantom" device at punit 1 would fail I/O operations. I went back to the drawing board, and came back with a vengeance further cleanup diff.

Date: Sun, 20 Nov 2005 18:27:56 +0000
From: Miod Vallat
To: Jason Downs, Todd C. Miller, Theo de Raadt
Subject: hp300: simplify HPIB CS/80 devices probe

This probably does not matter to many people those days, but I am
attempting to bring the hp300 HP-IB code in par with other disk
subsystems (and yes, I am finding bugs in the process).

However, not having much HP-IB hardware left in working condition, I
wouldn't mind extra eyes on my changes even if you are not able to test.

This diff attempts to make sure we don't match() HP-IB devices, later to
find out in attach() that there really isn't a device at the current
locators. This only affects CS/80 devices, which are the only ones which
may span several ``punits'' at the same bus ``slave'' (enclosure ID).

In order to do this, we need the xxident() routines to be able to
perform the describe command by themselves. This is straightforward for
ct, while hd needs hdreset() modified... but then you'll notice hdreset
was using the softc pointer only to access never-used-elsewhere structs
in the softc, which are a breeze to move to the stack (they are small
enough for us to afford this).

Then, ok, we have the describe command results. What to do then? Well,
if the returned status (variable stat) is zero, the data is valid. If it
is nonzero, we have a problem, but it could be a temporary disk failure
while it spins up, yet the data may be correct. So I decided to consider
a device as non-existing if the describe result buffer has a zero device
id (which I believe is supposed never to happen for valid CS/80 devices,
but I won't bet anything on it) and status is nonzero.

Running with this diff (and a shitload of further changes), I still get
my existing devices probed as expected, but now the second, non
existing, drive in my 9122C unit does not appear anymore - instead of
being matched, dummy attached, and not even failing read commands!

Comments?

Miod

PS: Note that since we now always have real hardware when we attach hd,
HDF_ALIVE can go. This is done in a further diff, at this point I prefer
to keep this diff simple enough.

[...]

I don't remember why I sat on that diff for more than one year; I was probably not confident enough in it yet.

Also, still not having been able to find any other HP-IB disk, at this point I opened the HP-IB disk enclosure, and noticed that inside was an ESDI disk, with a logic board doing the appropriate command conversion. Since there are HP-IB commands to query the drive geometry, I tried to replace the disk with a larger one: a 310MB Micropolis 1558. Unfortunately, this did not work as well as I would have hoped.

Date: Wed, 23 Nov 2005 22:49:34 +0000
From: Miod Vallat
To: Theo de Raadt, Todd C. Miller
Subject: [hpib] muhahahahaha

See? Replacing that old 80MB drive with a oh-so-much-larger Micropolis
1558 stolen in the Sun 4/260 enclosure (it doesn't need it, really, I
boot off SMD disks)...

[...]
nhpib0 at dio0 scode 7 ipl 3: internal HP-IB
hpibbus0 at nhpib0
hd0 at hpibbus0 slave 0 punit 0: stat 1 name: 79580 ('079580')
  iuw 8001, maxxfr 1000, ctype 0
  utype 0, bps 256, blkbuf 64, burst 0, blktime 472
  avxfr 900, ort 80, atp 500, maxint 1, fv 1, rv 0
  maxcyl/head/sect 1212/14/34, maxvsect 636824, inter 1
hd0: 7946A
hd0: 52MB, 968 cyl, 7 head, 16 sec, 512 bytes/sec, 108416 sec total
[...]

The fun part being it will correctly report geometry (1213/15/35 instead
of 1013/5/63 for the real 7957) but not the real sector size (256
instead of the real 512). Moreover, for some reason, the ID changes from
7957 to 7958, which causes it to be misdetected. That's pretty
strange...

Miod

Note that the geometry reported on the last hd0 line is completely different. This is because the HP-IB disk driver contains a table of device id it will recognize as disks, with their geometry, and uses that information. It looks like this:

struct  hdidentinfo {
        short   ri_hwid;                /* 2 byte HW id */
        short   ri_maxunum;             /* maximum allowed unit number */
        char    *ri_desc;               /* drive type description */
        int     ri_nbpt;                /* DEV_BSIZE blocks per track */
        int     ri_ntpc;                /* tracks per cylinder */
        int     ri_ncyl;                /* cylinders per unit */
        int     ri_nblocks;             /* DEV_BSIZE blocks on disk */
};
[...]
/*
 * Misc. HW description, indexed by sc_type.
 * Nothing really critical here, could do without it.
 */
const struct hdidentinfo hdidentinfo[] = {
        { HD7946AID,    0,      "7945A",        NHD7945ABPT,
          NHD7945ATRK,  968,     108416 },

        { HD9134DID,    1,      "9134D",        NHD9134DBPT,
          NHD9134DTRK,  303,      29088 },

        { HD9134LID,    1,      "9122S",        NHD9122SBPT,
          NHD9122STRK,  77,        1232 },

        { HD7912PID,    0,      "7912P",        NHD7912PBPT,
          NHD7912PTRK,  572,     128128 },

        { HD7914PID,    0,      "7914P",        NHD7914PBPT,
          NHD7914PTRK,  1152,    258048 },

        { HD7958AID,    0,      "7958A",        NHD7958ABPT,
          NHD7958ATRK,  1013,    255276 },
[...]
};

Note the value of the second field, ri_maxnum: when nonzero, it concerns dual-disk devices. This could have been used as a hint to probe another physical unit number; but since I had opted to do an exhaustive probe, this field no longer had any usefulness (and eventually got removed).

After sitting on my cleanup diff for more than one year, I finally completed it with a few more changes and commited it in february 2007.

Long awaiting [sic] modernization:
- reset the drive and fetch its identification strings during probe, and do
  not attach if they don't look good.
- do not store synchronous command blocks (used by hdreset) in the softc,
  since they are not processed asynchronously. The stack will do.
- cleanup the disklabel retrieval code.
- use disk_{,un}lock instead of rolling our own equivalent.
- use bounds_check_with_label() in hdstrategy() instead of a stripped-down
  inline version of it.
Tested on 7957A.

I had forgotten the tape driver part of my original diff, though; this was fixed three weeks later.

Some years later, a friend of mine had a need for HP-IB cables, so I gave him all my cables, which in turn became a convenient excuse to get rid of the scarce HP-IB hardware I had. And the OpenBSD/hp300 port got discontinued in 2014.

But the NetBSD/hp300 port is still alive. Early 2021, Izumi Tsutsui discovered the HPDrive project, which allows a computer with an HP-IB controller to act as an HP-IB disk drive for another machine, and he reported success with NetBSD.

As he tried different hardware models emulated by HPDrive, he eventually noticed that only the first unit of emulated dual-devices would show up in NetBSD. I pointed him to the OpenBSD changes which had solved this issue, and they landed in NetBSD later that year.

A few more fixes were made or ported from OpenBSD some time later, to improve interoperability with HPDrive.


The new config code has proved itself versatile enough, over the years, to still be in use today, coping with removable devices and power management. It is unlikely to need to be overhauled again.

Yet, because weird or quirky hardware still exist today, and non-enumerable busses are also still in use on cheap hardware, there is always a risk of subtle misbehaviours in the code, when exposed to new hardware setups.

I don't think there will be device configuration bugs surviving 8 years anymore, though, and that's a good thing!