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?
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.
while ((device_pointer->interesting_register & SPECIAL_BIT_TO_CHECK) == 0)
DELAY(1000);
temporary_variable = device_pointer->interesting_register;
while ((temporary_variable & SPECIAL_BIT_TO_CHECK) == 0)
DELAY(1000);
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 ?
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 ?
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
};
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
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.
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.
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.
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
rd* at hpibbus? slave ? punit ?
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
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
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!