Writing Drivers For NetBSD
Writing Drivers For NetBSD
Jochen Kunz
Version 1.0.1e
August 25th, 2003
1
CONTENTS 2
Contents
1 Preface 4
A rf.c 51
B rfreg.h 80
C License 83
D Version History 84
E Bibliography 85
C Index 86
List of Figures
1 device tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2 calling chain of autoconf(9) functions . . . . . . . . . . . . . . 11
3 rf(4)’s interal states. . . . . . . . . . . . . . . . . . . . . . . . . 41
1 PREFACE 4
1 Preface
This document is intended to teach the basics of Unix-Kernelprogramming to a
beginning Programmer with basic C knowledge. As an example, a device driver
for a floppy drive under NetBSD was chosen, as the hardware and necessary doc-
umentation was available but the driver itself missing. NetBSD was chosen as the
target operating system, as it lends itself perfectly as a teaching example due to its
clearly structured source code and well defined interfaces.
Unfortunately, there is hardly any specific documentation on Unix-
Kernelprogramming under NetBSD apart from references to the functions in sec-
tion 9 of the NetBSD manual pages. These manual pages are however miss-
ing an introduction, a document that clarifies the connection between the various
functions. This document attempts to provide such an introduction, to act as the
necessary glue between the individual parts. Therefore, I will reference external
documents, particularly section 9 of the NetBSD manual pages in many places.
This document is mainly based on the experiences I made when I wrote the driver
rf(4)1 for the UnixBus / QBus RX211 8” floppy controller.
8” floppy? Those things existed? Yes. Those were the first floppies, built at
the end of the 60s / beginning of the 70s. UniBus / QBus? Whatsdat? That’s the
most common bus found in VAXen2 . The VAX was the machine of the late 70s
up until the beginning of the 90s. Then it was obsoleted by the Alpha architecture.
BSD Unix has a long and glorious history on the VAX. [McK 99] But why am I
writing a driver today for such antiquated technology? In the end, it doesn’t really
matter if I explain the necessary steps using the latest 1GBit/s Ethernetcard for
a PCIX Bus or anything else. The underlying principles are the same. Besides,
the hardware used in this example is relatively simplistic, so that we can see the
essential aspects instead of being hindered by PeeCee idiocrasy.
The following chapter gives a short overview of the autoconf(9) concept in
NetBSD. Some details have been omitted, and I refer to the according manual
pages to avoid duplication of information.
The third chapter documents the implementation of the autoconf(9) inter-
face of rf(4).
The fourth and last chapter covers the actual driver, i.e. the functionality of
the driver carrying the data from and to the physical device.
In the appendix, you will find the complete source code of the driver as well
1 RX01/02 Floppy
2 plural for VAX
1 PREFACE 5
2.1 config(8)
There is one central file which declares the kernel configuration for a BSD Unix
Kernel. Under NetBSD, this file is located in sys/arch/<arch>/conf. <arch>
represents the appropriate machine- / processor architecture. In our example, this
is vax, i.e. d.h. sys/arch/vax/conf. In this folder, you can find the kernel con-
figuration file GENERIC, which contains all the drivers and options supported by
this architecture. You can create a user-defined configuration file by copying the
file to a new name in this directory and editing it. Usually, this means commenting
out all the drivers for devices not available in the particular machine. This process
can be automated by using the tool pkgsrc/sysutils/adjustkernel.
After calling config(8), it reads the kernel configuration file to determine
which drivers / functionality should be included in the kernel. Some ”‘files.*”’
files assign the .c- and .h-files to the various drivers and functionality. Us-
ing these dependencies, config(8) creates a compilation directory containing
a Makefile as well as a range of .c- and .h-files. The .h-files usually contain
defines with parameters such as the max. number of driver instances (for exam-
ple PseudoTTYs, BPF, ...), kernel options such as KTRACE, ... The file param.c
also falls into this category.
The compilation directory is named after the kernel configuration file and is
located in sys/arch/vax/compile. After changing into said directory, the actual
compilation is started by the command make depend netbsd. See config(8)
and http://www.netbsd.org/Documentation/kernel/ for details.
mainbus0 at root
2 THE AUTOCONF(9) SYSTEM 7
scsibus* at asc?
scsibus* at si?
We quickly realize that the organization of the device drivers can be repre-
sented in a treelike structure as in figure 1. Attached to the imaginary root
(which appears “out of nowhere”) we find the first child, the abstract mainbus.
This mainbus is parent to the children ibus, sbi and vsbus. These children in
turn are parent to uba, le, asc, ... These relationships represent the above men-
tioned cfdata table found in the file ioconf.c. The programmer does not need
to know or care about this table, as it is automagically created by config(8) .
It is important to realize that each device (node) has a parent (except for root,
due to the old chicken-or-the-egg problem). A node that has children, is a Bus or
a controller. The actual devices are the leaves of the tree. Each leaf and each node
represent a device driver. That of course means, that there must be drivers for the
2 THE AUTOCONF(9) SYSTEM 8
uda0
uba0 qu0
rlc0 rl*
ibus0 ze0
le0
bus systems as well. The bus drivers are, especially in this context, responsible
for locating the devices attached to the bus (i.e. the “busscan”).
Another important realization lies in the fact that there are several different
ways of arriving at the same driver! For example le0: root => mainbus0
=> ibus0 => le0 or root => mainbus0 => vsbus0 => le0. le is the actual
driver for the LANCE Ethernet Chip. Since the core of the driver accesses the
hardware only through abstract,bus-independent functions, the special details of
the hardware are hidden from it. Instead of manipulating the hardware directly,
the driver utilizes abstract handles. These handles and the according functions are
provided by the parent (i.e. the driver of the bussystem). Of course, all possible
parents (vsbus and ibus in this case) for a given child (le, in this case) need to
provide the same interfaces.3 These interfaces and the dependencies among the
drivers and the other kernel subsystems are described in more detail via so-called
attributes further down.
direct configuration The bus adapter hardware provides a complete list of all
currently available physically available child-devices. By reading the “PCI
configuration space”, a bus driver can find out which PCI devices are cur-
rently available and thus only pull in the drivers for those devices.
indirect configuration With the QBus or ISA, the second case applies. With
these busses, the driver can not simply loop through all the bus addresses to
determine which devices do (and do not) exist.
In the case of indirect configuration, the bus driver has to utilize the
config search() function. config search() walks down the potential child
device drivers in cfdata, i.e. it will call the foo match() function of all potential
child device drivers. So the bus- / controller driver calls config search() only
once to find all the child devices. The config attach() function of the driver
of the located child device will therefore not be called. So the bus- / controller
driver would have to search the cfdata table for the just found children and call
config attach() for those. But I have not found any driver that does this. Es de-
scribed under autoconf(9) in the section on config search(), you can achieve
the same by using the func function parameter of config search(). This func
2 THE AUTOCONF(9) SYSTEM 11
indirect direct
config config
config_search config_found_sm
1 10 1
4
func config_search
2 2 10
3 3 5
9
foo_match 4 foo_match
config_attach config_attach
if print() != NULL 6
6 8 7 9
5 7 8
function is provided by the bus- / controller driver. config search() calls this
function for all the child device drivers found in the cfdata table. That function,
on the other hand, calls the foo match() function of the child device driver and,
should the child have located the device, the config attach() function of the
child device driver. If a bus / controller supports direct configuration, as for ex-
ample PCI or PNP-ISA, the bus driver calls config found sm() once for each
child device. This function first calls config search() and then, if a child has
been found, config attach(). The config attach() function reserves mem-
ory for the softc structure of the child device drivers and calls its foo attach()
function. In this case, the submatch() is often NULL, or the config found()
function is called immediately. The parent provides the print function , and
passes a pointer to this function to config found sm(). The print function is
called from within config attach(), after config attach() has printed a mes-
sage like “foo at bar”. The name parameter of the print function is then NULL.
The print function should be used to output more detailed information on the
console about the child device, such as the exact device type, data transfer rate, ...
If this is not desired, NULL may be passed in place of the function pointer.
But why do we look for the children using config search(), if the bus- /
controller driver already noticed the presence of any child device? Well, while the
child device may without any doubt be present, the question remains whether or
not a driver for it is included in the current kernel. If there is no driver for this child
2 THE AUTOCONF(9) SYSTEM 12
device, then config search() fails and print (provided by the parent) is called
directly instead of config attach(). During this call, the name parameter of the
print function is a pointer to the name of the parents device. The print function
should then print a message like “foo at bar”. Usually, foo attach would
print this message, but since no driver for foo is included in the kernel, there
is no foo attach function. The print then returns either UNCONF or UNSUPP
. Either the message “not configured” or “unsupported” will be appended
to the message printed by print accordingly. If the driver does exist (at least
in principle), but was not compiled into the current kernel, UNCONF is returned;
UNSUPP, if the parent detects a child and knows that there isn’t a driver available
for it. Another reason for the use of config search() and foo match() under
direct configuration is explained in section 3.3.3. But that detail shall confuse use
only lateron. ;-)
Any questions? Hell yeah! Where exactly does the driver call
config found() or config search() from? Well, that’s easy: in its own
foo attach() function. foo attach() initializes the driver, and looking for
your children is part of the initialization.
All these pieces fall into place if, armed with this knowledge, you ven-
ture into the kernel source code and take a close look at the autoconf(9)
interface as well as the involved “files.*” files. Also of interest is
sys/kern/subr autoconf.c, as well as the config search() (+mapply()),
config found sm() and config attach() functions (you don’t need to com-
pletely understand config attach() just yet). These few lines of code in
sys/kern/subr autoconf.c are what it’s all about.
That’s all we need to add to the kernels configuration file to activate the driver.
The csr is the locator of the UniBus / QBus. It is given in octal and represents the
address of the device under which it identifies itself on the QBus.
3.1.2 sys/dev/qbus/files.uba
Then we need to enter our driver and the files that implement it in
sys/dev/qbus/files.uba5:
the Majordevice numbers of the character- and block device nodes. Therefore, needs-flag is
absolutely necessary in those versions.
3 THE AUTOCONF(9) PART OF THE RF(4) DRIVER 16
available under the same name in UNIX V6 since 1976. See [Li 77]
3 THE AUTOCONF(9) PART OF THE RF(4) DRIVER 17
nommap,
nokqfilter,
D_DISK
};
These functions implement the different operations possible with the device
in question, such as open, close, write, ioctl.... If a driver does not
implement one of these functions, it writes no<functionname> in its place, for
example nommap. The last field in the cdevsw or bdevsw data structure is the de-
vice type. Currently, the following types are available: D DISK, D TAPE, D TTY.
Block devices are understandably always of type D DISK. The device type deter-
mines which functions a driver has to implement:
D DISK: open, close, read, write, ioctl
D TAPE: open, close, read, write, ioctl
D TTY: open, close, read, write, ioctl, stop, tty, poll
D DISK and D TAPE therefore do not need to provide stop, tty, poll. (Ques-
tion to all gurus: What about drivers, which also implement mmap(2)?) As we
will see below, there are preprocessor macros to simplify the declaration of these
functions. All these macros and the prepared data structures can be found in
sys/sys/conf.h.
dev_type_open(rfopen);
dev_type_close(rfclose);
dev_type_read(rfread);
dev_type_write(rfwrite);
dev_type_ioctl(rfioctl);
dev_type_strategy(rfstrategy);
dev_type_dump(rfdump);
dev_type_size(rfsize);
CFATTACH_DECL(
rfc,
sizeof(struct rfc_softc),
rfc_match,
rfc_attach,
NULL,
NULL
);
CFATTACH_DECL(
rf,
sizeof(struct rf_softc),
rf_match,
rf_attach,
NULL,
NULL
);
And finally the struct, through which the rfc parent tells its rf child at
autoconfig(9) time its actual found “bus address”. More on this under section
3.3.3, when we talk about rf match.
struct rfc_attach_args {
u_int8_t type; /* controller type, 1 or 2 */
u_int8_t dnum; /* drive number, 0 or 1 */
};
In order to communicate with the controller, it maps a two byte wide “regis-
ters” (the so-called command and status registers) into the address space of the
bus.10 As the name suggests, it is possible to send a specific command to the con-
troller by sending a certain combination of bits via bus space write 2(9) or to
query its status via bus space read 2(9). Depending on the command, multiple
writes may be necessary to provide all parameters such as sector number, etc.
int
rfc_match(struct device *parent, struct cfdata *match, void *aux)
{
struct uba_attach_args *ua = aux;
int i;
First we send a reset to the controller and wait up to two seconds if it ac-
knowledges the command. Should the controller end the reset properly, a second
command with an interrupt enable bit is sent. Why are we using an interrupt, if
driver(9) states that the entire autoconfig(9) procedure takes place when no
interrupts have been enabled yet? Well, this only means that a driver can not yet
use any interrupts, since the interrupt handling of the kernel has not yet been ini-
tialized. A device can, however, cause an interrupt nonetheless, it will just remain
in the depth of the hard-/software. The interrupt gets lost, the interrupt handler is
not called. We’ll cover interrupts in more details lateron.
In this case, this is actually necessary. A driver for a QBus device has to
cause an interrupt in its foo match() function. This interrupt is caught by
the QBus bus driver and is the only possibility for it to determine the inter-
rupt level and -vector of the device. Therefore, the QBus bus driver does give
an error message at boot time, if a foo match() function indicates the pres-
ence of a device but no interrupt has taken place. (All of this is handled in the
function sys/dev/qbus/uba.c:ubasearch(), which is the func parameter of
config search in the QBus bus driver. See 2.5.)
3 THE AUTOCONF(9) PART OF THE RF(4) DRIVER 21
void
rfc_attach(struct device *parent, struct device *self, void *aux)
{
struct rfc_softc *rfc_sc = (struct rfc_softc *)self;
struct uba_attach_args *ua = aux;
struct rfc_attach_args rfc_aa;
int i;
rfc_sc->sc_iot = ua->ua_iot;
rfc_sc->sc_ioh = ua->ua_ioh;
rfc_sc->sc_dmat = ua->ua_dmat;
rfc_sc->sc_curbuf = NULL;
/* Tell the QBus busdriver about our interrupt handler. */
uba_intr_establish(ua->ua_icookie, ua->ua_cvec, rfc_intr, rfc_sc,
&rfc_sc->sc_intr_count);
/* Attach to the interrupt counter, see evcnt(9) */
evcnt_attach_dynamic(&rfc_sc->sc_intr_count, EVCNT_TYPE_INTR,
ua->ua_evcnt, rfc_sc->sc_dev.dv_xname, "intr");
/* get a bus_dma(9) handle */
i = bus_dmamap_create(rfc_sc->sc_dmat, RX2_BYTE_DD, 1, RX2_BYTE_DD, 0,
BUS_DMA_ALLOCNOW, &rfc_sc->sc_dmam);
if (i != 0) {
printf("rfc_attach: Error creating bus dma map: %d\n", i);
return;
}
Passing another reset to initialize the device at this point is a “Good Idea” (C)
(R) (TM), since an “attach” routine must not rely on any “pre-requisites” of the
“match” routine.
3 THE AUTOCONF(9) PART OF THE RF(4) DRIVER 22
rfc_aa.type = 1;
}
printf(": RX0%d\n", rfc_sc->type);
The last task remaining is to look for the children, i.e. to determine if, where
and how any floppy drives are attached. Those are then integrated into the device
tree by means of config found().
#ifndef RX02_PROBE
/*
* Both disk drives and the controller are one physical unit.
* If we found the controller, there will be both disk drives.
* So attach them.
*/
rfc_aa.dnum = 0;
rfc_sc->sc_childs[0] = config_found(&rfc_sc->sc_dev, &rfc_aa,rf_print);
rfc_aa.dnum = 1;
rfc_sc->sc_childs[1] = config_found(&rfc_sc->sc_dev, &rfc_aa,rf_print);
#else /* RX02_PROBE */
/*
* There are clones of the DEC RX system with standard shugart
* interface. In this case we can not be sure that there are
* both disk drives. So we want to do a detection of attached
* drives. This is done by reading a sector from disk. This means
* that there must be a formated disk in the drive at boot time.
* This is bad, but I did not find an other way to detect the
* (non)existence of a floppy drive.
*/
if (rfcprobedens(rfc_sc, 0) >= 0) {
rfc_aa.dnum = 0;
rfc_sc->sc_childs[0] = config_found(&rfc_sc->sc_dev, &rfc_aa,
rf_print);
} else
rfc_sc->sc_childs[0] = NULL;
if (rfcprobedens(rfc_sc, 1) >= 0) {
rfc_aa.dnum = 1;
rfc_sc->sc_childs[1] = config_found(&rfc_sc->sc_dev, &rfc_aa,
rf_print);
} else
3 THE AUTOCONF(9) PART OF THE RF(4) DRIVER 24
rfc_sc->sc_childs[1] = NULL;
#endif /* RX02_PROBE */
return;
}
3.3.3 rf match()
Since the rfc driver supports direct configuration, is calls config found() (and
through it rf match()) only if the device is without a doubt present. You may
think to yourself: “No problem, the device is present. We can reduce the function
rf match to a measly return( 1);”, but you would be wrong.
int
rf_match(struct device *parent, struct cfdata *match, void *aux) {
struct rfc_attach_args *rfc_aa = aux;
if ( match->cf_loc[RFCCF_DRIVE] == RFCCF_DRIVE_DEFAULT ||
match->cf_loc[RFCCF_DRIVE] == rfc_aa->dnum ) {
return( 1);
}
return( 0);
}
Why this check? Or rather, what is being checked? The match data struc-
ture of type cfdata describes the autoconfig(9) parameter for this driver from
the kernels configuration file. The cf loc array of the cfdata data structure
contains the value of the locators. The position of the locators in this array is
given by config(8). In order to be able to access the value of a certain locator
in this array, there are preprocessor constants which follow this naming conven-
tion: <ATTR>CF_<LOC>, with <ATTR> representing the name of the interface at-
tribute to which the locator belongs, and <LOC> representing the desired locator.
The rf driver attaches to the rfc interface attribute with the drive locator, i.e.
RFCCF DRIVE. This way, the driver can directory access the values of the drive
locator given in the kernel configuration file. For example, if it contains:
rf0 at rfc0 drive 1
then the value of match->cf_loc[RFCCF_DRIVE] is 1. If the kernel configura-
tion file does not assign a value to the locator, a wildcard is used, so that the value
os set to the standard value given in the file files.uba. This standard value is
3 THE AUTOCONF(9) PART OF THE RF(4) DRIVER 25
3.3.4 rf attach()
void
rf_attach(struct device *parent, struct device *self, void *aux)
{
struct rf_softc *rf_sc = (struct rf_softc *)self;
struct rfc_attach_args *rfc_aa = (struct rfc_attach_args *)aux;
struct rfc_softc *rfc_sc;
struct disklabel *dl;
3 THE AUTOCONF(9) PART OF THE RF(4) DRIVER 26
From an autoconf(9) view, this is nothing special aside from the initializa-
tion of the softc and other data structures. The first assignment shows how a
child device can access the softc data structure of its parent. disk attach(9)
and the following assignments initialize the disklabel.
4 THE CORE OF THE DRIVER 27
sc dev always has to be the first field of the softc data structure. This is re-
quired by autoconf(9).
sc childs contains pointers to the two possible child devices. (Each controller
can manage two drives at most.)
sc intr count The interrupt caller. Mainly used for statistical purposes and
is not strictly necessary for the driver to function but should always be used.
sc curchild Contains the number of the child device, currently being worked
on by the controller.
sc bytesleft How many bytes are left in the buffer and have to be transferred
from / to the floppy.
type 1 or 2, depending on if it’s a RX01 or a RX02.
struct rf_softc {
struct device sc_dev; /* common device data */
struct disk sc_disk; /* common disk device data */
struct bufq_state sc_bufq; /* queue of pending transfers */
int sc_state; /* state of drive */
u_int8_t sc_dnum; /* drive number, 0 or 1 */
};
4 THE CORE OF THE DRIVER 29
These are some helper functions of the driver. The first is used to send a
command to the controller, as the name suggests. The last one is the interrupt
handler and, together with rfstrategy() provides the main functionality. The
second is a helper function of the interrupt handler.
This is just a helper function for debugging purposes. Also see the comments
in rfc attach.
Now, before we get into in the annoying little details of each function, a short
overview of the basic process flow within the driver: In order to be able to do any-
thing with the device, we need to first call rfopen(). This always happens and is
essential to the driver, since it’s the only way for it to initialize itself. Note that the
open() function of a driver may appear several times after another, for example
in order to open different partitions of a disk or several ports on a serial multi-port
card (with different minor device numbers) as well as to transfer data over a file
handle while receiving instructions from another via ioctl (with the same minor
device numbers). Either a process from user-space that opens the device node or
the kernel itself (for example through mount(2)) initiates the open() function
call.
Noteworthy: Each time a user or a kernel process opens the device node (for
example through a mount(2) oder in the context of RAIDframe(9)) the open()
function is called. But the close() function is not called until the last user of
the device executes a close(). For example: Three processes, A, B, and C open
one after another the same device node and keep it open, leading to three calls of
the open() function. Process A closes the device node - the driver doesn’t care.
Process C closes the device node – still, the driver doesn’t care. Only after process
B closed the device node, the close() function call is made, since this process
was the last one to have an open handle on the device node.
Data transfer from and to the hardware are handled by the rfstrategy()
function. This is the drivers point of access for data transfer, provided by the
driver to the kernel. On each call, rfstrategy() receives a pointer to a single
buffer. These infamous buffers are used by the buffer cache and describe a block
oriented data transfer, i.e. which data should be read from RAM at which position
/ address on which device, or what should be read from where in RAM.
4 THE CORE OF THE DRIVER 31
The strategy() function itself usually does not perform any I/O operations,
but performs a few checks and simply organizes the I/O operations. Basically, this
boils down to sorting the buffers in the bufferqueue (thus strategy). Other routines
then walk down the bufferqueues buffer by buffer. The number of these routines,
what exactly they do and how they do it is very specific to each driver. The kernel
does not tell the driver how to do this.
Typically, there exists at least one helper functions aside from strategy():
the interrupt handler. Compared to the CPU, I/O devices are rather slow. There-
fore, you can’t just simply wait until an I/O operation is finished. Instead, the
driver initiates an operation and kernel does other things, such as dispatching the
processing cycles etc. When the hardware has finished the I/O operation, it causes
an interrupt. The “normal” program flow is thus interrupted by an electronic sig-
nal by the hardware and a special routine inside the kernel is called. This routine
determines which piece of hardware has caused the interrupt and calls the inter-
rupt handler responsible for the piece of hardware that caused the interrupt. The
interrupt handler in turn finished the I/O operation by checking the error code of
the hardware (for example a read error of the floppy), moving indices within the
buffer, taking the next buffer out of the queue, etc.
4.2.1 rfdump()
... is used by the kernel to barf a core dump onto the disk or into swapspace, if
it needs to abort. I think it’s improbable that a floppy with 0.5MB capacity will
be sufficient to cause this. Therefore, this function simply consists of return(
ENXIO);. Also see errno(2).
4.2.2 rfsize()
Gives back the (swap) partitions size in chunks of DEV BSIZE size.
4.2.3 rfopen()
rfopen(dev_t dev, int oflags, int devtype, struct proc *p)
{
struct rf_softc *rf_sc;
struct rfc_softc *rfc_sc;
struct disklabel *dl;
int unit;
4 THE CORE OF THE DRIVER 32
unit = DISKUNIT(dev);
if (unit >= rf_cd.cd_ndevs || (rf_sc = rf_cd.cd_devs[unit]) == NULL) {
return(ENXIO);
}
First, rfopen() needs to determine if the device does actually exist, and if
so, it needs to fetch the softc data structure for the device. The path from the
device number to the softc data structure is, as you can see, relatively easy, once
you know it. The DISKUNIT() macro uses the minor device number to deter-
mine which instance number of the device is addressed by the device number.
See sys/disklabel.h. This is where the cfdriver data structure comes into
play. For each device mentioned in the kernels configuration file, config(8)
creates an instance of the data structur in ioconf.[ch] inside the kernel compi-
lation directory, using a naming convention of <DEV>_cd. Here, <DEV> represents
the name given in the kernel configuration file, rf in this case. cd stands for
ConfigurationDriver. The type definitions of this data structure can be found in
sys/device.h. The cd defs field of this data structure is an array of pointers,
pointing to a softc data structure of an instance of the device. Since device
may be added or removed during kernel runtime, this array is dynamic. The field
cd ndevs contains the total number of instances for the device (i.e. number of
fields in cd devs). Furthermore, there’s cd name, a pointer to a string with the
name of the device and an enum describing the device class, cd class.
Typically, you mark the open state in the softc data structure and block the
manual ejection. This way, you can prevent multiple simultaneous accesses of the
same device and remember if the device has been opened in a special mode. The
rf(4) driver takes advantage of this by assigning different minor device numbers
(i.e. partitions for a disk driver) to single or double write speeds or determining
the write speed automatically from the format of the floppy disk.
The drive has not been initialized at the first open call, that is, a sector needs
to be read from the floppy in order to determine if the inserted medium has the
appropriate density (or in order to determine the actual density when using “auto
density”). Therefore, the driver enters a RFS PROBING state and reports the floppy
disk as being “busy”11 . Afterwards, the controller receives a command to read the
sector and the driver waits for the (non) successful return of this command.
The problem with this: You can just loop around using DELAY() in
rfc match() (busy wait), which would mean that the entire machine is com-
pletely frozen. We are inside the kernel!12
The proper solution to the problem are the functions tsleep(9) and
wakeup(9). rfopen() was caused by some process, and that process needs to
wait until the operation has completed. Other processes can happily continue.
tsleep(9) marks the process that caused the rfopen() operation as “sleeping”
and tells the scheduler to allow the other processes to use the CPU until the oper-
ation has completed.
This completion then happens inside the interrupt handler. The controller, as
we know, received a read-command via the interrupt enable (RX2CS IE), meaning
the controller causes an interrupt as soon as the command has completed, which
in turn causes the call of the interrupt handler. The interrupt handler checks the
11 Using disk busy is not necessary strictly speaking, but certainly a nice thing to do and can
be used to determine statistical data; see iostat(8).
12 In rfc match(), this is feasible, as it only happens during boot.
4 THE CORE OF THE DRIVER 35
result of the command, manipulates the state variable of the driver accordingly
and tells the scheduler via wakeup(9), that the operation has completed and the
sleeping processes can be awakened. The next time the process gets any cycles on
the CPU, it continues inside rfopen() at the same point at which tsleep(9) was
called. Therefore, this is the spot at which we need to check whether or not the
interrupt handler has determined a successful completion or if there was a timeout.
A driver for a medium supporting partitions should make sure to read the
disklabel(5,9)s at this point. Since we do not support disklabel(5,9) on
RX01/02 floppies, we just create pseudo disklabel(5,9).
4.2.4 rfclose()
int
rfclose(dev_t dev, int fflag, int devtype, struct proc *p)
{
struct rf_softc *rf_sc;
int unit;
4 THE CORE OF THE DRIVER 36
unit = DISKUNIT(dev);
if (unit >= rf_cd.cd_ndevs || (rf_sc = rf_cd.cd_devs[unit]) == NULL) {
return(ENXIO);
}
if ((rf_sc->sc_state & 1 << (DISKPART(dev) + RFS_OPEN_SHIFT)) == 0)
panic("rfclose: can not close on non-open drive %s "
"partition %d", rf_sc->sc_dev.dv_xname, DISKPART(dev));
else
rf_sc->sc_state &= ˜(1 << (DISKPART(dev) + RFS_OPEN_SHIFT));
if ((rf_sc->sc_state & RFS_OPEN_MASK) == 0)
rf_sc->sc_state = 0;
return(0);
}
Closing a device that hasn’t been opened before, is a serious problem, and
causes a kernel panic. In the other case, we simply reset the bit that marks the
partition as opened. The last if statement checks, if all partitions have been closed
and if so, all the blocks created by rfopen need to be removed and the softc data
structur needs to be reinitialized.
4.2.6 rfstrategy()
As mentioned above, this function is the main part of the driver with respect to
data transfer. It takes the buffers and fills or empties them, but doesn’t perform
any IO operations itself. Instead, rfstrategy() writes these IO requests into one
or more queues. It’s the responsibility of the driver to sort the queues in a way
that reduces seeks of the read/write-head. The queues are emptied by the interrupt
handler. Each time, the hardware has completed an IO operation, it causes an
interrupt. The interrupt handler then registers it as completed and removes it from
the queue. Then, it initiates the next IO operation waiting in the queue, ...
i = DISKUNIT(buf->b_dev);
if (i >= rf_cd.cd_ndevs || (rf_sc = rf_cd.cd_devs[i]) == NULL) {
buf->b_flags |= B_ERROR;
buf->b_error = ENXIO;
biodone(buf);
return;
}
signaled when a buffer is ready using biodone(9). If an error occurs, you set a
flag and pass an appropriate error code accordingly (as we can see above).
if (buf->b_bcount == 0) {
biodone(buf);
return;
}
/*
* BUFQ_PUT() operates on b_rawblkno. rfstrategy() gets
* only b_blkno that is partition relative. As a floppy does not
* have partitions b_rawblkno == b_blkno.
*/
buf->b_rawblkno = buf->b_blkno;
/*
* from sys/kern/subr_disk.c:
* Seek sort for disks. We depend on the driver which calls us using
* b_resid as the current cylinder number.
*/
i = splbio();
if (rfc_sc->sc_curbuf == NULL) {
rfc_sc->sc_curchild = rf_sc->sc_dnum;
rfc_sc->sc_curbuf = buf;
rfc_sc->sc_bufidx = buf->b_un.b_addr;
rfc_sc->sc_bytesleft = buf->b_bcount;
rfc_intr(rfc_sc);
4 THE CORE OF THE DRIVER 39
} else {
buf->b_resid = buf->b_blkno / RX2_SECTORS;
BUFQ_PUT(&rf_sc->sc_bufq, buf);
buf->b_resid = 0;
}
splx(i);
return;
}
Let’s assume, rfstrategy() receives three buffers shortly after one another.
The first reads a sector from track 1, the seconds reads a sector from the last track
and the thirds reads a sector from somewhere in the middle. If rfstrategy()
blindly followed the order in which it received the buffers, it would need to move
the read/write head first to track 1, then all the way across the disk to the last
track and then back to the middle. But moving the read/write head is slow, i.e.
“expensive”. So we try to achieve minimal movement, which is why we want to
sort the buffer queue such that we work the first buffer first, then the third and
finally the second buffer. This way, reading the sector in the middle happens kind
of “on the way”. Fortunately, the driver doesn’t need to care too much about
sorting the buffers and leaves that to BUFQ PUT(). BUFQ PUT() expects the track-
or cylinder number (depending on the disk geometry) in the b resid field in order
to optimize it according to the cylinder numbers. BUFQ PUT() then enters the
buffer into the buffer queue and sorts is appropriately.
The entire sorting process is, however, not necessary if the controller is cur-
rently idle. In that case, rfc sc->sc curbuf == NULL. As the name suggests,
rfc sc->sc curbuf points to the buffer currently being worked on. If the con-
troller is idle, then the buffer is pointed to the current one and all related variables
within softc are initialized with it. If a new buffer arrives while the first is be-
ing worked on in rfc sc->sc curbuf, they are put in the buffer queue. Calling
rfc intr() initiates the actual data transfer. The rest is done in rfc intr() in
the context of an interrupt.
Interruptcontext, what’s that exactly anyway, and are there other contexts?
Simplified, there are three contexts under Unix:
Usercontext: A normal user process utilizes the CPU. The time is the same as
”‘xx% user”’ under top(1) or time(1).
Kernelcontext: A userland process has made a system call. Kernel code in priv-
ileged mode is executed, but still under the current process space. The time
4 THE CORE OF THE DRIVER 40
The alert reader already will have noticed a problem here: an interrupt is an
asynchronous event, which may happen at any time. For example, at the time
that rfstrategy() manipulates the bufferqueue, adding a new buffer. But the
interrupt handler of the rf(4) driver also manipulates the buffer queue (it removes
a buffer). If this happens, then the buffer queue ends up in an inconsistent state.
Booom, kernel panic, game over.
This is why rfstrategy() has to use splbio(9) in order to assure that no
interrupt can occur at this time. This function blocks all interrupts until they are re-
leased by a call to splx(9). The time, during which interrupts are blocked should
always be kept to a minimum, since not only interrupts for this particular device
are blocked, but all interrupts! If interrupt blocks are prolonged, an interrupt for a
different device might be delayed, reducing the I/O rate.
To be more precise: There are different interrupt priorities. IPL BIO, for ex-
ample, is rather low and is used by block devices such as disks and floppies. These
devices are slow anyway and usually have bigger buffers in their hardware on the
controller. So it doesn’t matter, if the device waits a little bit longer on its in-
terrupts. Network cards use IPL NET, which has higher priority over IPL BIO.
Network cards are “faster” than disks (no mechanics) and buffer overflows in a
network card are more expensive, as they can cause TCP retries and thus increase
the network load and decrease throughput. These priorities make it possible for
an interrupt of a network card to, well, interrupt the interrupt handler of the disk
driver, have the network cards driver handle the interrupt and the continue with
the interrupt handler of the disk driver.
So, it is not only important to block interrupts only for a short period of time,
but also with the right priority. If the priority is too low, then your own interrupt
handler can’t take the interrupt, if it’s too high, other devices throughput may
suffer. For more information on the relationship among the different priorities,
see spl(9).
4 THE CORE OF THE DRIVER 41
RFS_NOTINIT
OK Error
RFS_PROBING
Close
RFS_IDLE
RFS_FBUF RFS_RSEC
RFS_WSEC RFS_EBUF
Figure 3: rf(4)’s interal states.
These commands correspond to the RFS RSEC, RFS EBUF, RFS FBUF,
RFS WSEC states. The four other commands RX2CS SMD, RX2CS RSTAT,
RX2CS WDDS, RX2CS REC and their states are not used by the driver at this point.
RFS RSEC does not always have to follow RFS EBUF. If the buffer has been fin-
ished and the buffer queue is empy, then the driver enters the RFS IDLE state. if
the buffer queue is not empty, but a new buffer containing data that needs to be
written follows, a transition from RFS EBUF to takes place. Similarly, there are
transitions from RFS WSEC to RFS IDLE or RFS RSEC. The frc sendcmd function
simplifies the sending of commands a little bit.
Due to the length of this function, we only give an explanation of the basics
and some excerpts with special meaning: The functions consists of tw switch
statements. The first takes care of finding the last command / state and finish the
last operation. The second initializes the next command. Both switch statements
reside within a loop, which is aborted if a new command has successfully been
sent to the controller or the buffer queue is found to be empty. If an error occurred,
the interrupt handler continues at the beginning of the loop and tries to work the
next buffer. (Some other drivers contains a goto, but since I prefer spaghetti over
spaghetti code, I chose the loop.)
The following are the excerpts of the program that might be encountered
when performing a read starting with the RFS IDLE state, beginning with the
get new buf() helper function.
struct rf_softc*
get_new_buf( struct rfc_softc *rfc_sc)
{
struct rf_softc *rf_sc;
struct rf_softc *other_drive;
The if-statement is a security check. The RFS SETCMD macro simplifies setting
the rf sc->sc state variable. Depending on whether the current buffer contains
a read or a write command, it contains RFS RSEC or RFS FBUF.
The first instructions computes the logical block number to be read by the
floppy and stores it in i. Note that the RX02 drive does not use the usual 512
(DEV BSIZE, the value of buf->b blkno), but 128 Bytes per sector (RX2 BYTE SD)
in single and 256 Bytes per sector (RX2 BYTE DD) in double density. The if-
statement checks of the sector requested by the buffer is higher than the capacity
of the floppy and if so, sets the error flag in the buffer and aborts the operation.
See the explanation in 4.2.3 for details regarding disk busy().
Well, and finally the controller receives the command to read a sector
(RX2CS RSEC) with the interrupt bit RX2CS IE enabled via rfc sendcmd(). If
this fails, then the error handler a the end of the loop around the two switch
statements jumps in:
The rfc intr() function checks if the error flag has been set after each of the
two switch statements and brings the driver into a defined state in the case of an
error:
4 THE CORE OF THE DRIVER 45
Ok, the interrupt handler has finished and we can return to our old context...
until the controller has finished the command an causes an interrupt. Then we
continue with the interrupt handler of the rf(4) driver:
First we need to tell the kernel that the drive is no longer busy, that so far 0
Bytes have been transferred and that we received a read command. The if state-
ment checks the error flag (RX2CS ERR) in the CSR of the controller and aborts the
process if necessary. At this point, we could use the RX2CS RSTAT and RX2CS REC
commands to add some more detailed error diagnostics.
there is no such MMU inside the bus adapter, then we are stuck using a “bounce
buffer”, as under ISA (see [Tho]). But we do not need to care about all this when
writing a driver, bus dmamap load(9) takes care of this.
The driver can register the floppy as busy as long as the bus dma(9) system
hands a DMA map to the driver and the actual DMA operation can be initiated
using the RX2CS EBUF command. And once again, we wait for the next interrupt...
This call to rfc intr() ends the transfer. disk unbusy(9) tells the guys is
statistics how many bytes have been read, the DMA map is freed and the usual
error checks are made.
if (rfc_sc->sc_bytesleft > i) {
rfc_sc->sc_bytesleft -= i;
rfc_sc->sc_bufidx += i;
The buffer is not quite empty yet, so we need to advance the pointer and pre-
pare the next RFS RSEC command...
} else {
biodone(rfc_sc->sc_curbuf);
rf_sc = get_new_buf( rfc_sc);
if (rf_sc == NULL)
4 THE CORE OF THE DRIVER 48
return;
}
RFS_SETCMD(rf_sc->sc_state,
(rfc_sc->sc_curbuf->b_flags & B_READ) != 0
? RFS_RSEC : RFS_FBUF);
break;
Ah! The buffer has successfully and completely been finished, so we can tell
the rest of the kernel via biodone(9) so. Then we need to check if other buffers
are waiting in the buffer queue and if so, work on those. If there are no more
buffers in the queue for this drive, set it to idle and switch to the other drive,
in case new buffers have been added to that drives queue, while we were busy
working on the first drive.
4.2.8 rfioctl()
This function is the entrance point for the ioctl(2) calls of the device. In this
case, only die IOCTLs to read the disklabel(5,9) are absolutely necessary, all
other IOCTLs are not required or do not make sense in this driver. The RX02 drive
can not be blocked by the software, the medium can not be ejected, the hardware
is incapable of performing a low-level format...
int
rfioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p)
{
struct rf_softc *rf_sc;
int unit;
unit = DISKUNIT(dev);
if (unit >= rf_cd.cd_ndevs || (rf_sc = rf_cd.cd_devs[unit]) == NULL) {
return(ENXIO);
}
/* We are going to operate on a non open dev? PANIC! */
if (rf_sc->sc_open == 0) {
panic("rfstrategy: can not operate on non-open drive %s (2)",
rf_sc->sc_dev.dv_xname);
}
switch (cmd) {
4 THE CORE OF THE DRIVER 49
return(ENOTTY);
}
A RF.C 51
A rf.c
/*
* Copyright (c) 2002 Jochen Kunz.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of Jochen Kunz may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY JOCHEN KUNZ
* ‘‘AS IS’’ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JOCHEN KUNZ
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
TODO:
- Better LBN bound checking, block padding for SD disks.
- Formating / "Set Density"
- Better error handling / detaild error reason reportnig.
*/
/* autoconfig stuff */
#include <sys/param.h>
A RF.C 52
#include <sys/device.h>
#include <sys/conf.h>
#include "locators.h"
#include "ioconf.h"
/* bus_space / bus_dma */
#include <machine/bus.h>
/* disk interface */
#include <sys/types.h>
#include <sys/disklabel.h>
#include <sys/disk.h>
/* autoconfig stuff */
static int rfc_match(struct device *, struct cfdata *, void *);
static void rfc_attach(struct device *, struct device *, void *);
static int rf_match(struct device *, struct cfdata *, void *);
static void rf_attach(struct device *, struct device *, void *);
static int rf_print(void *, const char *);
rfdump,
rfsize,
D_DISK
};
struct rfc_softc {
struct device sc_dev; /* common device data */
struct device *sc_childs[2]; /* child devices */
struct evcnt sc_intr_count; /* Interrupt counter for statistics */
struct buf *sc_curbuf; /* buf that is currently in work */
bus_space_tag_t sc_iot; /* bus_space IO tag */
bus_space_handle_t sc_ioh; /* bus_space IO handle */
bus_dma_tag_t sc_dmat; /* bus_dma DMA tag */
bus_dmamap_t sc_dmam; /* bus_dma DMA map */
caddr_t sc_bufidx; /* current position in buffer data */
int sc_curchild; /* child whose bufq is in work */
int sc_bytesleft; /* bytes left to transfer */
u_int8_t type; /* controller type, 1 or 2 */
};
CFATTACH_DECL(
rfc,
A RF.C 55
sizeof(struct rfc_softc),
rfc_match,
rfc_attach,
NULL,
NULL
);
struct rf_softc {
struct device sc_dev; /* common device data */
struct disk sc_disk; /* common disk device data */
struct bufq_state sc_bufq; /* queue of pending transfers */
int sc_state; /* state of drive */
u_int8_t sc_dnum; /* drive number, 0 or 1 */
};
CFATTACH_DECL(
rf,
sizeof(struct rf_softc),
rf_match,
rf_attach,
NULL,
NULL
);
struct rfc_attach_args {
u_int8_t type; /* controller type, 1 or 2 */
u_int8_t dnum; /* drive number, 0 or 1 */
};
/* helper functions */
int rfc_sendcmd(struct rfc_softc *, int, int, int);
struct rf_softc* get_new_buf( struct rfc_softc *);
static void rfc_intr(void *);
/*
* Issue a reset command to the controller and look for the bits in
* RX2CS and RX2ES.
* RX2CS_RX02 and / or RX2CS_DD can be set,
* RX2ES has to be set, all other bits must be 0
*/
int
rfc_match(struct device *parent, struct cfdata *match, void *aux)
{
struct uba_attach_args *ua = aux;
int i;
/*
* Issue a Read Status command with interrupt enabled.
* The uba(4) driver wants to catch the interrupt to get the
* interrupt vector and level of the device
*/
bus_space_write_2(ua->ua_iot, ua->ua_ioh, RX2CS,
RX2CS_RSTAT | RX2CS_IE);
/*
* Wait for command to finish, ignore errors and
* abort if the controller does not respond within the timeout
*/
for (i = 0 ; i < 20 ; i++) {
if ((bus_space_read_2(ua->ua_iot, ua->ua_ioh, RX2CS)
& (RX2CS_DONE | RX2CS_IE)) != 0
&& (bus_space_read_2(ua->ua_iot, ua->ua_ioh, RX2ES)
& RX2ES_RDY) != 0 )
return(1);
DELAY(100000); /* wait 100ms */
}
return(0);
}
/* #define RX02_PROBE 1 */
#ifdef RX02_PROBE
/*
* Probe the density of an inserted floppy disk.
* This is done by reading a sector from disk.
* Return -1 on error, 0 on SD and 1 on DD.
*/
int rfcprobedens(struct rfc_softc *, int);
int
rfcprobedens(struct rfc_softc *rfc_sc, int dnum)
{
int dens_flag;
int i;
dens_flag = 0;
do {
A RF.C 58
return(-1);
}
#endif /* RX02_PROBE */
void
rfc_attach(struct device *parent, struct device *self, void *aux)
{
struct rfc_softc *rfc_sc = (struct rfc_softc *)self;
struct uba_attach_args *ua = aux;
struct rfc_attach_args rfc_aa;
int i;
rfc_sc->sc_iot = ua->ua_iot;
rfc_sc->sc_ioh = ua->ua_ioh;
rfc_sc->sc_dmat = ua->ua_dmat;
rfc_sc->sc_curbuf = NULL;
/* Tell the QBus busdriver about our interrupt handler. */
uba_intr_establish(ua->ua_icookie, ua->ua_cvec, rfc_intr, rfc_sc,
&rfc_sc->sc_intr_count);
/* Attach to the interrupt counter, see evcnt(9) */
evcnt_attach_dynamic(&rfc_sc->sc_intr_count, EVCNT_TYPE_INTR,
ua->ua_evcnt, rfc_sc->sc_dev.dv_xname, "intr");
/* get a bus_dma(9) handle */
i = bus_dmamap_create(rfc_sc->sc_dmat, RX2_BYTE_DD, 1, RX2_BYTE_DD, 0,
BUS_DMA_ALLOCNOW, &rfc_sc->sc_dmam);
if (i != 0) {
printf("rfc_attach: Error creating bus dma map: %d\n", i);
return;
}
& RX2CS_DONE) != 0
&& (bus_space_read_2(rfc_sc->sc_iot, rfc_sc->sc_ioh, RX2ES)
& (RX2ES_RDY | RX2ES_ID)) != 0)
break;
DELAY(100000); /* wait 100ms */
}
/*
* Give up if the timeout has elapsed
* and the controller is not ready.
*/
if (i >= 20) {
printf(": did not respond to INIT CMD\n");
return;
}
/* Is ths a RX01 or a RX02? */
if ((bus_space_read_2(rfc_sc->sc_iot, rfc_sc->sc_ioh, RX2CS)
& RX2CS_RX02) != 0) {
rfc_sc->type = 2;
rfc_aa.type = 2;
} else {
rfc_sc->type = 1;
rfc_aa.type = 1;
}
printf(": RX0%d\n", rfc_sc->type);
#ifndef RX02_PROBE
/*
* Bouth disk drievs and the controller are one physical unit.
* If we found the controller, there will be bouth disk drievs.
* So attach them.
*/
rfc_aa.dnum = 0;
rfc_sc->sc_childs[0] = config_found(&rfc_sc->sc_dev, &rfc_aa,rf_print);
rfc_aa.dnum = 1;
rfc_sc->sc_childs[1] = config_found(&rfc_sc->sc_dev, &rfc_aa,rf_print);
#else /* RX02_PROBE */
/*
* There are clones of the DEC RX system with standard shugart
* interface. In this case we can not be sure that there are
* bouth disk drievs. So we want to do a detection of attached
A RF.C 61
int
rf_match(struct device *parent, struct cfdata *match, void *aux)
{
struct rfc_attach_args *rfc_aa = aux;
/*
* Only attach if the locator is wildcarded or
* if the specified locator addresses the current device.
*/
if (match->cf_loc[RFCCF_DRIVE] == RFCCF_DRIVE_DEFAULT ||
match->cf_loc[RFCCF_DRIVE] == rfc_aa->dnum)
return(1);
return(0);
}
A RF.C 62
void
rf_attach(struct device *parent, struct device *self, void *aux)
{
struct rf_softc *rf_sc = (struct rf_softc *)self;
struct rfc_attach_args *rfc_aa = (struct rfc_attach_args *)aux;
struct rfc_softc *rfc_sc;
struct disklabel *dl;
dl->d_partitions[0].p_size = 501;
dl->d_partitions[0].p_offset = 0; /* starting sector */
dl->d_partitions[0].p_fsize = 0; /* fs basic fragment size */
dl->d_partitions[0].p_fstype = 0; /* fs type */
dl->d_partitions[0].p_frag = 0; /* fs fragments per block */
dl->d_partitions[1].p_size = RX2_SECTORS * RX2_TRACKS / 2;
dl->d_partitions[1].p_offset = 0; /* starting sector */
dl->d_partitions[1].p_fsize = 0; /* fs basic fragment size */
dl->d_partitions[1].p_fstype = 0; /* fs type */
dl->d_partitions[1].p_frag = 0; /* fs fragments per block */
dl->d_partitions[2].p_size = RX2_SECTORS * RX2_TRACKS;
dl->d_partitions[2].p_offset = 0; /* starting sector */
dl->d_partitions[2].p_fsize = 0; /* fs basic fragment size */
dl->d_partitions[2].p_fstype = 0; /* fs type */
dl->d_partitions[2].p_frag = 0; /* fs fragments per block */
bufq_alloc(&rf_sc->sc_bufq, BUFQ_DISKSORT | BUFQ_SORT_CYLINDER);
printf("\n");
return;
}
int
rf_print(void *aux, const char *name)
{
struct rfc_attach_args *rfc_aa = aux;
if (name != NULL)
aprint_normal("RX0%d at %s", rfc_aa->type, name);
aprint_normal(" drive %d", rfc_aa->dnum);
return(UNCONF);
}
void
rfstrategy(struct buf *buf)
{
struct rf_softc *rf_sc;
struct rfc_softc *rfc_sc;
A RF.C 65
int i;
i = DISKUNIT(buf->b_dev);
if (i >= rf_cd.cd_ndevs || (rf_sc = rf_cd.cd_devs[i]) == NULL) {
buf->b_flags |= B_ERROR;
buf->b_error = ENXIO;
biodone(buf);
return;
}
rfc_sc = (struct rfc_softc *)rf_sc->sc_dev.dv_parent;
/* We are going to operate on a non open dev? PANIC! */
if ((rf_sc->sc_state & 1 << (DISKPART(buf->b_dev) + RFS_OPEN_SHIFT))
== 0)
panic("rfstrategy: can not operate on non-open drive %s "
"partition %d", rf_sc->sc_dev.dv_xname,
DISKPART(buf->b_dev));
if (buf->b_bcount == 0) {
biodone(buf);
return;
}
/*
* BUFQ_PUT() operates on b_rawblkno. rfstrategy() gets
* only b_blkno that is partition relative. As a floppy does not
* have partitions b_rawblkno == b_blkno.
*/
buf->b_rawblkno = buf->b_blkno;
/*
* from sys/kern/subr_disk.c:
* Seek sort for disks. We depend on the driver which calls us using
* b_resid as the current cylinder number.
*/
i = splbio();
if (rfc_sc->sc_curbuf == NULL) {
rfc_sc->sc_curchild = rf_sc->sc_dnum;
rfc_sc->sc_curbuf = buf;
rfc_sc->sc_bufidx = buf->b_un.b_addr;
rfc_sc->sc_bytesleft = buf->b_bcount;
rfc_intr(rfc_sc);
} else {
buf->b_resid = buf->b_blkno / RX2_SECTORS;
A RF.C 66
BUFQ_PUT(&rf_sc->sc_bufq, buf);
buf->b_resid = 0;
}
splx(i);
return;
}
/*
* Look if there is an other buffer in the bufferqueue of this drive
* and start to process it if there is one.
* If the bufferqueue is empty, look at the bufferqueue of the other drive
* that is attached to this controller.
* Start procesing the bufferqueue of the other drive if it isn’t empty.
* Return a pointer to the softc structure of the drive that is now
* ready to process a buffer or NULL if there is no buffer in either queues.
*/
struct rf_softc*
get_new_buf( struct rfc_softc *rfc_sc)
{
struct rf_softc *rf_sc;
struct rf_softc *other_drive;
} else
return(NULL);
}
return(rf_sc);
}
void
rfc_intr(void *intarg)
{
struct rfc_softc *rfc_sc = intarg;
struct rf_softc *rf_sc;
int i;
RFS_SETCMD(rf_sc->sc_state,
RFS_NOTINIT);
wakeup(rf_sc);
}
} else {
printf("%s: density error.\n",
rf_sc->sc_dev.dv_xname);
RFS_SETCMD(rf_sc->sc_state,RFS_NOTINIT);
wakeup(rf_sc);
}
}
return;
case RFS_IDLE: /* controller is idle */
if (rfc_sc->sc_curbuf->b_bcount
% ((rf_sc->sc_state & RFS_DENS) == 0
? RX2_BYTE_SD : RX2_BYTE_DD) != 0) {
/*
* can only handle blocks that are a multiple
* of the physical block size
*/
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
}
RFS_SETCMD(rf_sc->sc_state, (rfc_sc->sc_curbuf->b_flags
& B_READ) != 0 ? RFS_RSEC : RFS_FBUF);
break;
case RFS_RSEC: /* Read Sector */
disk_unbusy(&rf_sc->sc_disk, 0, 1);
/* check for errors */
if ((bus_space_read_2(rfc_sc->sc_iot, rfc_sc->sc_ioh,
RX2CS) & RX2CS_ERR) != 0) {
/* should do more verbose error reporting */
printf("rfc_intr: Error reading sector: %x\n",
bus_space_read_2(rfc_sc->sc_iot,
rfc_sc->sc_ioh, RX2ES) );
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
}
RFS_SETCMD(rf_sc->sc_state, RFS_EBUF);
break;
case RFS_WSEC: /* Write Sector */
i = (rf_sc->sc_state & RFS_DENS) == 0
A RF.C 69
? RX2_BYTE_SD : RX2_BYTE_DD;
disk_unbusy(&rf_sc->sc_disk, i, 0);
/* check for errors */
if ((bus_space_read_2(rfc_sc->sc_iot, rfc_sc->sc_ioh,
RX2CS) & RX2CS_ERR) != 0) {
/* should do more verbose error reporting */
printf("rfc_intr: Error writing sector: %x\n",
bus_space_read_2(rfc_sc->sc_iot,
rfc_sc->sc_ioh, RX2ES) );
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
break;
}
if (rfc_sc->sc_bytesleft > i) {
rfc_sc->sc_bytesleft -= i;
rfc_sc->sc_bufidx += i;
} else {
biodone(rfc_sc->sc_curbuf);
rf_sc = get_new_buf( rfc_sc);
if (rf_sc == NULL)
return;
}
RFS_SETCMD(rf_sc->sc_state,
(rfc_sc->sc_curbuf->b_flags & B_READ) != 0
? RFS_RSEC : RFS_FBUF);
break;
case RFS_FBUF: /* Fill Buffer */
disk_unbusy(&rf_sc->sc_disk, 0, 0);
bus_dmamap_unload(rfc_sc->sc_dmat, rfc_sc->sc_dmam);
/* check for errors */
if ((bus_space_read_2(rfc_sc->sc_iot, rfc_sc->sc_ioh,
RX2CS) & RX2CS_ERR) != 0) {
/* should do more verbose error reporting */
printf("rfc_intr: Error while DMA: %x\n",
bus_space_read_2(rfc_sc->sc_iot,
rfc_sc->sc_ioh, RX2ES));
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
}
RFS_SETCMD(rf_sc->sc_state, RFS_WSEC);
break;
case RFS_EBUF: /* Empty Buffer */
A RF.C 70
/*
* ... then initiate next command.
*/
switch (rf_sc->sc_state & RFS_CMDS) {
case RFS_EBUF: /* Empty Buffer */
i = bus_dmamap_load(rfc_sc->sc_dmat, rfc_sc->sc_dmam,
rfc_sc->sc_bufidx, (rf_sc->sc_state & RFS_DENS) == 0
? RX2_BYTE_SD : RX2_BYTE_DD,
rfc_sc->sc_curbuf->b_proc, BUS_DMA_NOWAIT);
if (i != 0) {
printf("rfc_intr: Error loading dmamap: %d\n",
i);
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
break;
}
disk_busy(&rf_sc->sc_disk);
if (rfc_sendcmd(rfc_sc, RX2CS_EBUF | RX2CS_IE
| ((rf_sc->sc_state & RFS_DENS) == 0 ? 0 : RX2CS_DD)
| (rf_sc->sc_dnum == 0 ? 0 : RX2CS_US)
| ((rfc_sc->sc_dmam->dm_segs[0].ds_addr
& 0x30000) >>4), ((rf_sc->sc_state & RFS_DENS) == 0
? RX2_BYTE_SD : RX2_BYTE_DD) / 2,
rfc_sc->sc_dmam->dm_segs[0].ds_addr & 0xffff) < 0) {
disk_unbusy(&rf_sc->sc_disk, 0, 1);
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
bus_dmamap_unload(rfc_sc->sc_dmat,
rfc_sc->sc_dmam);
A RF.C 72
}
break;
case RFS_FBUF: /* Fill Buffer */
i = bus_dmamap_load(rfc_sc->sc_dmat, rfc_sc->sc_dmam,
rfc_sc->sc_bufidx, (rf_sc->sc_state & RFS_DENS) == 0
? RX2_BYTE_SD : RX2_BYTE_DD,
rfc_sc->sc_curbuf->b_proc, BUS_DMA_NOWAIT);
if (i != 0) {
printf("rfc_intr: Error loading dmamap: %d\n",
i);
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
break;
}
disk_busy(&rf_sc->sc_disk);
if (rfc_sendcmd(rfc_sc, RX2CS_FBUF | RX2CS_IE
| ((rf_sc->sc_state & RFS_DENS) == 0 ? 0 : RX2CS_DD)
| (rf_sc->sc_dnum == 0 ? 0 : RX2CS_US)
| ((rfc_sc->sc_dmam->dm_segs[0].ds_addr
& 0x30000)>>4), ((rf_sc->sc_state & RFS_DENS) == 0
? RX2_BYTE_SD : RX2_BYTE_DD) / 2,
rfc_sc->sc_dmam->dm_segs[0].ds_addr & 0xffff) < 0) {
disk_unbusy(&rf_sc->sc_disk, 0, 0);
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
bus_dmamap_unload(rfc_sc->sc_dmat,
rfc_sc->sc_dmam);
}
break;
case RFS_WSEC: /* Write Sector */
i = (rfc_sc->sc_curbuf->b_bcount - rfc_sc->sc_bytesleft
+ rfc_sc->sc_curbuf->b_blkno * DEV_BSIZE) /
((rf_sc->sc_state & RFS_DENS) == 0
? RX2_BYTE_SD : RX2_BYTE_DD);
if (i > RX2_TRACKS * RX2_SECTORS) {
rfc_sc->sc_curbuf->b_flags |= B_ERROR;
break;
}
disk_busy(&rf_sc->sc_disk);
if (rfc_sendcmd(rfc_sc, RX2CS_WSEC | RX2CS_IE
| (rf_sc->sc_dnum == 0 ? 0 : RX2CS_US)
| ((rf_sc->sc_state& RFS_DENS) == 0 ? 0 : RX2CS_DD),
A RF.C 73
int
rfdump(dev_t dev, daddr_t blkno, caddr_t va, size_t size)
{
int
rfsize(dev_t dev)
{
return(-1);
}
int
rfopen(dev_t dev, int oflags, int devtype, struct proc *p)
{
struct rf_softc *rf_sc;
struct rfc_softc *rfc_sc;
A RF.C 75
unit = DISKUNIT(dev);
if (unit >= rf_cd.cd_ndevs || (rf_sc = rf_cd.cd_devs[unit]) == NULL) {
return(ENXIO);
}
rfc_sc = (struct rfc_softc *)rf_sc->sc_dev.dv_parent;
dl = rf_sc->sc_disk.dk_label;
switch (DISKPART(dev)) {
case 0: /* Part. a is single density. */
/* opening in single and double density is sensless */
if ((rf_sc->sc_state & RFS_OPEN_B) != 0 )
return(ENXIO);
rf_sc->sc_state &= ˜RFS_DENS;
rf_sc->sc_state &= ˜RFS_AD;
rf_sc->sc_state |= RFS_OPEN_A;
break;
case 1: /* Part. b is double density. */
/*
* Opening a singe density only drive in double
* density or simultaneous opening in single and
* double density is sensless.
*/
if (rfc_sc->type == 1
|| (rf_sc->sc_state & RFS_OPEN_A) != 0 )
return(ENXIO);
rf_sc->sc_state |= RFS_DENS;
rf_sc->sc_state &= ˜RFS_AD;
rf_sc->sc_state |= RFS_OPEN_B;
break;
case 2: /* Part. c is auto density. */
rf_sc->sc_state |= RFS_AD;
rf_sc->sc_state |= RFS_OPEN_C;
break;
default:
return(ENXIO);
break;
}
if ((rf_sc->sc_state & RFS_CMDS) == RFS_NOTINIT) {
A RF.C 76
rfc_sc->sc_curchild = rf_sc->sc_dnum;
/*
* Controller is idle and density is not detected.
* Start a density probe by issuing a read sector command
* and sleep until the density probe finished.
* Due to this it is impossible to open unformated media.
* As the RX02/02 is not able to format its own media,
* media must be purchased preformated. fsck DEC marketing!
*/
RFS_SETCMD(rf_sc->sc_state, RFS_PROBING);
disk_busy(&rf_sc->sc_disk);
if (rfc_sendcmd(rfc_sc, RX2CS_RSEC | RX2CS_IE
| (rf_sc->sc_dnum == 0 ? 0 : RX2CS_US)
| ((rf_sc->sc_state & RFS_DENS) == 0 ? 0 : RX2CS_DD),
1, 1) < 0) {
rf_sc->sc_state = 0;
return(ENXIO);
}
/* wait max. 2 sec for density probe to finish */
if (tsleep(rf_sc, PRIBIO | PCATCH, "density probe", 2 * hz)
!= 0 || (rf_sc->sc_state & RFS_CMDS) == RFS_NOTINIT) {
/* timeout elapsed and / or somthing went wrong */
rf_sc->sc_state = 0;
return(ENXIO);
}
}
/* disklabel. We use different fake geometries for SD and DD. */
if ((rf_sc->sc_state & RFS_DENS) == 0) {
dl->d_nsectors = 10; /* sectors per track */
dl->d_secpercyl = 10; /* sectors per cylinder */
dl->d_ncylinders = 50; /* cylinders per unit */
dl->d_secperunit = 501; /* sectors per unit */
/* number of sectors in partition */
dl->d_partitions[2].p_size = 500;
} else {
dl->d_nsectors = RX2_SECTORS / 2; /* sectors per track */
dl->d_secpercyl = RX2_SECTORS / 2; /* sectors per cylinder */
dl->d_ncylinders = RX2_TRACKS; /* cylinders per unit */
/* sectors per unit */
dl->d_secperunit = RX2_SECTORS * RX2_TRACKS / 2;
A RF.C 77
int
rfclose(dev_t dev, int fflag, int devtype, struct proc *p)
{
struct rf_softc *rf_sc;
int unit;
unit = DISKUNIT(dev);
if (unit >= rf_cd.cd_ndevs || (rf_sc = rf_cd.cd_devs[unit]) == NULL) {
return(ENXIO);
}
if ((rf_sc->sc_state & 1 << (DISKPART(dev) + RFS_OPEN_SHIFT)) == 0)
panic("rfclose: can not close on non-open drive %s "
"partition %d", rf_sc->sc_dev.dv_xname, DISKPART(dev));
else
rf_sc->sc_state &= ˜(1 << (DISKPART(dev) + RFS_OPEN_SHIFT));
if ((rf_sc->sc_state & RFS_OPEN_MASK) == 0)
rf_sc->sc_state = 0;
return(0);
}
int
rfread(dev_t dev, struct uio *uio, int ioflag)
{
int
A RF.C 78
int
rfioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p)
{
struct rf_softc *rf_sc;
int unit;
unit = DISKUNIT(dev);
if (unit >= rf_cd.cd_ndevs || (rf_sc = rf_cd.cd_devs[unit]) == NULL) {
return(ENXIO);
}
/* We are going to operate on a non open dev? PANIC! */
if ((rf_sc->sc_state & 1 << (DISKPART(dev) + RFS_OPEN_SHIFT)) == 0)
panic("rfioctl: can not operate on non-open drive %s "
"partition %d", rf_sc->sc_dev.dv_xname, DISKPART(dev));
switch (cmd) {
/* get and set disklabel; DIOCGPART used internally */
case DIOCGDINFO: /* get */
memcpy(data, rf_sc->sc_disk.dk_label,
sizeof(struct disklabel));
return(0);
case DIOCSDINFO: /* set */
return(0);
case DIOCWDINFO: /* set, update disk */
return(0);
case DIOCGPART: /* get partition */
((struct partinfo *)data)->disklab = rf_sc->sc_disk.dk_label;
((struct partinfo *)data)->part =
&rf_sc->sc_disk.dk_label->d_partitions[DISKPART(dev)];
return(0);
break;
case DIOCWFORMAT:
break;
return(ENOTTY);
}
B RFREG.H 80
B rfreg.h
/*
* Copyright (c) 2002 Jochen Kunz.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of Jochen Kunz may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY JOCHEN KUNZ
* ‘‘AS IS’’ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JOCHEN KUNZ
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* Bitdefinitions of CSR. */
#define RX2CS_ERR 0x8000 /* Error RO */
#define RX2CS_INIT 0x4000 /* Initialize WO */
#define RX2CS_UAEBH 0x2000 /* Unibus address extension high bit WO */
#define RX2CS_UAEBI 0x1000 /* Unibus address extension low bit WO */
#define RX2CS_RX02 0x0800 /* RX02 RO */
/* 0x0400 Not Used -- */
/* 0x0200 Not Used -- */
#define RX2CS_DD 0x0100 /* Double Density R/W */
#define RX2CS_TR 0x0080 /* Transfer Request RO */
#define RX2CS_IE 0x0040 /* Interrupt Enable R/W */
#define RX2CS_DONE 0x0020 /* Done RO */
#define RX2CS_US 0x0010 /* Unit Select WO */
#define RX2CS_FCH 0x0008 /* Function Code high bit WO */
#define RX2CS_FCM 0x0004 /* Function Code mid bit WO */
#define RX2CS_FCL 0x0002 /* Function Code low bit WO */
#define RX2CS_GO 0x0001 /* Go WO */
#define RX2CS_NU 0x0600 /* not used bits -- */
/* Bitdefinitions of RX2ES. */
/* <15-12> Not Used -- */
#define RX2ES_NEM 0x0800 /* Non-Existend Memory RO */
#define RX2ES_WCO 0x0400 /* Word Count Overflow RO */
/* 0x0200 Not Used RO */
#define RX2ES_US 0x0010 /* Unit Select RO */
#define RX2ES_RDY 0x0080 /* Ready RO */
#define RX2ES_DEL 0x0040 /* Deleted Data RO */
#define RX2ES_DD 0x0020 /* Double Density RO */
#define RX2ES_DE 0x0010 /* Density Error RO */
#define RX2ES_ACL 0x0008 /* AC Lost RO */
#define RX2ES_ID 0x0004 /* Initialize Done RO */
/* 0x0002 Not Used -- */
#define RX2ES_CRCE 0x0001 /* CRC Error RO */
#define RX2ES_NU 0xF202 /* not used bits -- */
C License
Copyright c 2003 Jochen Kunz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
THIS SOFTWARE IS PROVIDED BY JOCHEN KUNZ ‘‘AS IS’’ AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
D VERSION HISTORY 84
D Version History
1.0 First publication.
E Bibliography
References
[McK 99] Twenty Years of Berkeley Unix
From AT&T-Owned to Freely Redistributable
http://www.oreilly.com/catalog/opensources/book/kirkmck.html
86
INDEX 87