NetBSD 1.6 book

As mentioned on www.netbsd.org the german publisher C&L has made available a book on NetBSD 1.6. The book covers NetBSD installation (i386, macppc and others), user administration, updating the system, kernel tuning, configuring the rc.d startup system, package management using pkg_install and the pkgsrc tree, configuring the X window system and using NetBSD in a networked environment (IPv4/6, DNS, NFS/NIS, FTP, Samba and the like).

Published: 2003-01-30 — Comments

Introduction to NetBSD loadable kernel modules

Introduction

Loadable kernel modules (LKMs) are quite popular on most modern operating systems such as GNU/Linux, FreeBSD and of course Microsoft Windows, just to name a few. They offer you the possibility to extend the kernel’s functionality at runtime without recompiling or even rebooting the system. For example nearly every Linux device driver is available - or can be made available - as a loadable kernel module, that can be loaded at runtime to get support for a particular device (or even a pseudo-device).

With NetBSD, LKMs are not that popular yet. At the time of this writing only a few drivers are available as loadable modules (mostly filesystem and compat drivers, and a few others such as the linuxrtc emulation). This might change in near future.

The loadable kernel module interface was originally designed to be similar in functionality to the loadable kernel modules facility provided by SunOS. The lkm(4) facility is controlled by performing ioctl(2) calls on the /dev/lkm device, but since all operations are handled by the modload(8), modunload(8) and modstat(8) programs, you should never have to interact with /dev/lkm directly. Note, that you need to run a kernel compiled with the LKM option in order to make use of LKMs.

Writing the module

I’d like to show you how to write a simple character device driver that does nothing but the simple job of calculating the Fibonacci numbers (I’ll therefore name the module fibo.o and let all the function’s names begin with fibo_). The driver will provide 8 minor devices /dev/fibo0 to /dev/fibo7. Each minor device offers the following functions:

static int fibo_open(dev_t, int, int, struct proc *);
static int fibo_close(dev_t, int, int, struct proc *);
static int fibo_read(dev_t dev, struct uio *, int);

You can open and close a device provided by this driver and you’ll be able to read from it (we’ll have a closer look at the parameters later, when we discuss the actual functions). Now we need to tell the kernel that we provide a character device with the 3 functions listed above. Therefore we need to fill in a struct cdevsw (cdevsw means character device switch and the struct cdevsw is defined in sys/conf.h).

static struct cdevsw fibo_dev = {
  fibo_open,
  fibo_close,
  fibo_read,
  (dev_type_write((*))) enodev,
  (dev_type_ioctl((*))) enodev,
  (dev_type_stop((*))) enodev,
  0,
  (dev_type_poll((*))) enodev,
  (dev_type_mmap((*))) enodev,
  0
};

enodev is a generic function that simply returns the errno(2) ENODEV (Operation not supported by device) which says that we does not support any operations besides open, close and read. So, for example, whenever you try to write to the device, the write(2) will fail with ENODEV.

Furtheron we need to tell the kernel how the module is named and where to find information about operations provided by the module. This is a quite simple task with the lkm interface: We use the preprocessor macro MOD_DEV, which is defined in sys/lkm.h to hand the information over. The MOD_DEV macro was changed in NetBSD-current, therefore we use the following construct to get things working with both NetBSD 1.6 and earlier and NetBSD 1.6H and later (thanks to Anil Gopinath for the hint).

#if (__NetBSD_Version__ >= 106080000)
MOD_DEV("fibo", "fibo", NULL, -1, &fibo_dev, -1);
#else
MOD_DEV("fibo", LM_DT_CHAR, -1, &fibo_dev);
#endif

This means that our module is named fibo, we’ll provide a character device (minor devices are handled by the module itself, so they doesn’t matter for now), we want to retrieve a dynamic major device number from the kernel (if you want to use a specific major device number you’ll need to specify that instead of the -1, but beware of getting in conflict with other device drivers) and we provide the information about the supported operations in fibo_dev.

In order to ensure proper unloading of the module we need to keep a global reference counter of opened minor devices.

static int fibo_refcnt = 0;

And furtheron we need to keep a bunch of information about each minor device.

struct fibo_softc {
  int       sc_refcnt;
  u_int32_t sc_current;
  u_int32_t sc_previous;
};
 
#define MAXFIBODEVS 8
 
static struct fibo_softc fibo_scs[MAXFIBODEVS];

As mentioned above our driver will provide 8 minor devices. Each minor device stores information about how often it was opened (in our example each minor device can only be opened once to keep things simple), the current number and the previous number for calculating the Fibonacci numbers. If you don’t know how to calculate the Fibonacci numbers, you should have a look on a book about algorithms, as explaining this is beyond the scope of this article.

Each kernel module needs to have an entry point which is passed to ld(1) by modload when the module is linked. The default module entry point is named xxxinit. If xxxinit cannot be found, an attempt to use modulename_lkmentry will be made, where modulename is the filename of the module being loaded without the .o. In general the entry function will consist entirely of a single DISPATCH line, with DISPATCH being a preprocessor macro defined in sys/lkm.h to handle loading, unloading and stating for us. So our fibo_lkmentry function will look like this:

int
fibo_lkmentry(struct lkm_table *lkmtp, int cmd, nt ver)
{
  DISPATCH(lkmtp, cmd, ver, fibo_handle, fibo_handle, fibo_handle);
}

Now we need a handler function for our module to do module specific tasks when loading, unloading or stating the module. The name of this handler function is passed to DISPATCH (see above) to tell the kernel which function it has to call when doing such things. A pointer to the module entry in the LKM table and an integer representing the desired command (LKM_E_LOAD, LKM_E_UNLOAD or LKM_E_STAT) are passed to the handler function. The handler is called after the module is linked and loaded into the kernel with the LKM_E_LOAD command. Then we need to check whether the module was already loaded into the kernel and initialize our data structures. When unloading the module, the handler is called with the LKM_E_UNLOAD command and we need to check if the module is not required any more (e.g. check if all devices are closed for char/block driver modules) before confirming the unload command.

static int
fibo_handle(struct lkm_table *lkmtp, int cmd)
{
  switch (cmd) {
  case LKM_E_LOAD:
    /* check if module was already loaded */
    if (lkmexists(lkmtp))
      return (EEXIST);
      
    /* initialize minor device structures */
    bzero(fibo_scs, sizeof(fibo_scs));
    printf("fibo: FIBONACCI driver loaded successfully\n");
    break;
    
  case LKM_E_UNLOAD:
    /* check if a minor device is opened */
    if (fibo_refcnt > 0)
      return (EBUSY);
    break;
    
  case LKM_E_STAT:
    break;
    
  default:
    return (EIO);
  }
  
  return (0);
}

The open function is quite simple as most of the hard stuff is already handled by the NetBSD kernel (e.g. the kernel will automatically allocate a vnode(9) for you). The parameters for the open function are the major and minor device numbers (use the major and minor macros), the flag and mode arguments as described in open(2) and a pointer to the struct proc of the process that did the open system call.

So the first thing to do is to check if the minor device number we got when the device was opened is not out of range, and if the minor device is not already opened. You should always keep in mind that the minor device handling is completely up to you and that this is a never ending source of mistakes! Then we need to initialize the minor device data (the Fibonacci starting numbers 1, 0 + 1 = 1, 1 + 1 = 2, 1 + 2 = 3, …) and increase the minor device and the global module reference counter.

static int
fibo_open(dev_t dev, int flag, int mode, struct proc *p)
{
  struct fibo_softc *fibosc = (fibo_scs + minor(dev));
  
  if (minor(dev) >= MAXFIBODEVS)
    return (ENODEV);
    
  /* check if device already open */
  if (fibosc->sc_refcnt > 0)
    return (EBUSY);
    
  fibosc->sc_current = 1;
  fibosc->sc_previous = 0;
 
  /* increase device reference counter */
  fibosc->sc_refcnt++;
  
  /* increase module reference counter */
  fibo_refcnt++;
  
  return (0);
}

The close function has the same parameters with the same meanings as the open function described above. It is used to free the internal data structures of a minor device opened before. You do not need to worry whether the device was opened before or to do things like releasing the vnode associated with the device, all you need to do is to cleanup the module specific stuff. In our example this means decreasing the minor device and the global module reference counters and so that our close function is quite simple.

static int
fibo_close(dev_t dev, int flag, int mode, struct proc *p)
{
  struct fibo_softc *fibosc = (fibo_scs + minor(dev));
  
  /* decrease device reference counter */
  fibosc->sc_refcnt--;
  
  /* decrease module reference counter */
  fibo_refcnt--;
  
  return (0);
}

Last but not least the read function. This function has 3 parameters: the device major and minor numbers like in the open and close functions, a flag field indicating for example whether the read should be done in a non-blocking fashion or such things and a pointer to a struct uio defined in sys/uio.h. A struct uio typically describes data in motion, in case of a read(2) system call data moved from kernel-space to user-space. This may look a bit strange if you already did device driver progamming on GNU/Linux, but the uio concept used by the NetBSD kernel simplifies a lot of things and provides a generic and consistent interface for kernel-space to user-space and kernel-space to kernel-space data moving. See uiomove(9) for more information.

Back on stage, we should first have a look at the read function and discuss the details afterwards.

static int
fibo_read(dev_t dev, struct uio *uio, int flag)
{
  struct fibo_softc *fibosc = (fibo_scs + minor(dev));
  
  if (uio->uio_resid < sizeof(u_int32_t))
    return (EINVAL);
    
  while (uio->uio_resid >= sizeof(u_int32_t)) {
    int error;
    
    /* copy to user space */
    if ((error = uiomove(&(fibosc->sc_current),
    		    sizeof(fibosc->sc_current), uio))) {
      return (error);
    }
    
    /* prevent overflow */
    if (fibosc->sc_current > (MAXFIBONUM - 1)) {
      fibosc->sc_current = 1;
      fibosc->sc_previous = 0;
      continue;
    }
    
    /* calculate */ {
      u_int32_t tmp;
      
      tmp = fibosc->sc_current;
      fibosc->sc_current += fibosc->sc_previous;
      fibosc->sc_previous = tmp;
    }
  }
  
  return (0);
}

So the first thing we do, is to check whether the process requests less than sizeof(u_int32_t) bytes (actually 4 bytes). Our read function always reads a bunch of 4-byte blocks and to keep it simple and easy to understand we disallow reading less than 4 bytes at a time (uio->uio_resid holds the number of remaining bytes to move to user-space, automatically decreased by uiomove).

The function copies the current Fibonacci number into the user-space buffer, checks for a possible overflow (only the first 42 Fibonacci numbers fit into u_int32_t) and calculates the next Fibonacci number. If there is enough space left in the user-space buffer, the function loops and restarts the process of moving, checking and calculating until the buffer is filled up to the possible maximum or uiomove(9) returns an error. Note, that a read(2) system call on this device will never return 0, and so it will never reach an end-of-file (EOF), so the device will generate Fibonacci numbers forever.

If you’re familar with GNU/Linux device driver programming you might have noticed that we do not return -ERRNO on failure, and in case of the read system call the number of bytes read, but instead we return 0 on success and the positive errno value on failure. Everything else is handled by the NetBSD kernel itself, so we do not need to care about.

Loading the module

Now that our device driver module is completed, we need a shell script that will be executed when the module is successfully loaded to create the required device nodes in /dev. This shell script (or program) is always passed three arguments: the module id (in decimal), the module type (in hexadecimal) and the character major device number (this differs for other types of LKMs such as system call modules). So our script is pretty simple:

if [ $# -ne 3 ]; then
  echo "$0 should only be called from modload(8) with 3 args"
  exit 1
fi

First check whether all three command line arguments are present and exit with error code if not.

for i in 0 1 2 3 4 5 6 7; do
  rm -f /dev/fibo$i
  mknod /dev/fibo$i c $3 $i
  chmod 666 /dev/fibo$i
done
exit 0

And finally (re)create the required special device nodes. Now we are ready to give our module a first test run. Compile the module and load the module with the following command (this needs to be run as superuser):

modload -e fibo_lkmentry -p fibo_post.sh fibo.o

If everything went well, the modstat(8) program should present you output similar to this:

Type    Id  Off Loadaddr Size Info     Rev Module Name
DEV      0   29 dca4f000 0004 dca4f260   1 fibo

Testing the module

In order to test your new kernel module, we need a small test program that does nothing more than reading a 32bit unsigned integer value from /dev/fibo0 and outputs the value to standard output. See the sample program below:

#define DEVICE "/dev/fibo0"
 
int
main(int argc, char **argv)
{
  u_int32_t val;
  int fd, ret;
  
  if ((fd = open(DEVICE, O_RDONLY)) < 0)
    err(1, "unable to open " DEVICE);
    
  while ((ret = read(fd, &val, sizeof(val))) == sizeof(val))
    printf("%u\n", val);
    
  if (ret < 0)
    err(2, "read(" DEVICE ")");
    
  close(fd);
  return 0;
}

When you run this sample test program, it will output Fibonacci numbers below 2971215074 until you interrupt or kill the program. To unload the kernel module, you need to run the following command (as superuser):

modunload -n fibo

The complete sources for the example above, including a Makefile, are available online at:

A tar archive with the sources can be found here. I hope you like this small introduction to the NetBSD lkm system. If you have any questions or if you would like to give me some feedback, feel free to contact me.

Published: 2002-11-27 — Comments

Setting up KDE 3 on NetBSD

Introduction

Like most other Unix(TM)-like operating systems, the K Desktop Environment (KDE) is also available for NetBSD. KDE is a powerful graphical desktop environment based upon Trolltech’s QT library, and is similar to other desktop environments available for Unix(TM) workstations, such as the Common Desktop Environment (CDE) or GNOME. It tries to combine ease of use, contemporary functionality, and outstanding graphical design with the technological superiority of the Unix operating system. Like NetBSD itself, KDE is also an Internet project and is licensed under the GNU General Public License (GPL), and is therefore open source software.

Installation

It is quite easy to install the latest KDE 3 on a NetBSD system using either binary packages or building it from pkgsrc. As I wrote this, there were no official KDE 3 binary packages available on the ftp servers and I didn’t have the space and bandwidth to put my packages online :-(. It would be nice, if someone could provide me with a few 100MB of web/ftp space to put my packages on, so that everyone reading this could use these packages.

If you are not lucky to fetch some binary packages for KDE 3, then you should fetch a recent pkgsrc from NetBSD-current (must be a post 20020830) and start building KDE from scratch. Therefore you should change to the directory x11/kde3 in your pkgsrc tree and start by typing make install. NOTE: Before starting to install KDE 3, you should remove any installed KDE package and the installed QT packages (if they’re older than 3.0.5), and do a make clean on your pkgsrc tree, else the installation might fail. After starting the installation process, you’ll need to wait up to 3 days, until the installation is complete, depending on the hardware of your machine. You might want to speed up the installation process by mounting the filesystem that contains the pkgsrc tree with the async option; e.g. type mount -u -o async /usr/pkgsrc, but beware, that mounting a filesystem asyncronous may have a bad effect on your data if the computer crashes. So, you have been warned, use this option at your own risk.

Postinstallation

Now, that KDE 3 is installed properly, all you need to do, is setup your X session to start KDE 3. Therefore edit (maybe create) the file .xsession in your home directory and put in the following lines:

#!/bin/sh
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/pkg/bin:/usr/pkg/sbin:/usr/local/bin:/usr/local/sbin:/usr/X11R6/bin
exec /usr/X11R6/bin/startkde

Then mark the file executable by typing chmod +x $HOME/.xsession, login as usual with xdm and feel happy about your new and fancy KDE desktop :-).

If you’re using startx instead of xdm, you need to edit the file .xinitrc in your home directory instead.

Using antialiased fonts

You might also want to get KDE rendering those nice fonts you saw on all those screenshots all over the web. Therefore you’ll need to have XFree86 version 4.x installed (this is the default for NetBSD 1.6 and above). Then you need to install some nice looking TrueType or Type1 fonts, therefore you can use the freely available webfonts provided by Microsoft, simply install the fonts/ms-ttf package and the fonts/ttmkfdir package. Then goto /usr/X11R6/lib/X11/fonts/TrueType and type:

$ ttmkfdir > fonts.scale
$ mkfontdir

After that add the following line to your /usr/X11R6/lib/X11/XF86Config-4 in the Files section

FontPath "/usr/X11R6/lib/X11/fonts/TrueType/"

and

dir "/usr/X11R6/lib/X11/fonts/TrueType"

to your /usr/X11R6/lib/X11/XftConfig. Logout from your X session and restart your X server by hitting CTRL-ALT-BACKSPACE at your xdm login prompt. Relogin to your KDE desktop and enable “Use Anti-Aliasing for fonts” in the KDE control center; logout again and relogin, and choose some nice antialiased fonts (the ones with the [Xft] in the name) for your desktop. Enjoy! ;-).

Feedback

I hope you liked my little KDE 3 HowTo and I would be pleased to get some good (or even bad ;-) feedback from you. If you have any problems or questions on this topic, I’ll try to help you getting things to work.

Published: 2002-10-28 — Comments

How to use hardware monitors with NetBSD

Today, there are various environmental sensor ICs available on modern mainboards, which are able of monitoring fan speed, cpu and system temperature, and voltage for example. NetBSD currently supports the National Semiconductor LM78, LM79 and compatible hardware monitors (the lm(4) device), the VIA VT82C686A hardware monitor (the viaenv(4) device) and ACPI aware hardware monitors (the acpi(4) subsystem) through its Environmental Systems API. These devices are not enabled by default, you need to recompile your kernel with the following additional lines in your kernel config file:

For the National Semiconductor LM78, LM79 and compatible monitors (you may need to adjust the I/O port settings):

 
lm0 at isa? port 0x290

For the VIA VT82C686A hardware monitor:

 
viapm* at pci? dev ? function ?
viaenv* at viapm?

For ACPI enabled monitors (thanks to Joel Carnat for the hint):

 
options MPACPI
acpi0 at mainbus0
acpiacad at acpi?
acpibat* at acpi?
acpibut* at acpi?
acpiec* at acpi?
acpilid* at acpi?
acpitz* at acpi?

After booting your new kernel, you can access your hardware sensors, using the envstat(8) command. For example, to display all supported sensors, type envstat -l, and to actually read the environmental sensors, simply type envstat. For more information about using the envstat(8) tool, refer to the manpage.

Published: 2002-10-19 — Comments

Why NetBSD?

During the last years, I used to install a lot of different operating systems, looking for the system that fits my needs best. For example, I tried Microsoft Windows NT (3.51, 4.0 and 5.0 and had a quick look at 5.1), several Linux distributions (e.g. SuSE Linux, RedHat Linux, Debian GNU/Linux and Mandrake Linux), FreeBSD and OpenBSD. We also tried to write our own operating system (we named it “Socratix”), but soon we noticed, that it was an impossible task for us to write a whole operating system (from scratch).

All those systems I tried have their particular advantages, but of course they all have their native disadvantages. And not allowing me to fix these disadvantages (e.g. the Debian Policy is such a disadvantage, I think), makes me become very frustrated of open source software (and the philosophy behind it). So after all, I decided to give NetBSD another try (I had installed NetBSD several times before, for testing reasons, but never get really close to it). After reading Federico Lupi’s excellent NetBSD Guide I fetched the NetBSD distribution, created an iso image out of it and burned it onto a cd. Starting off the very straight-forward installation procedure, I was very pleased with NetBSD. It allows to me to do exactly what I want to do, nothing more, and nothing less. The base system is very clean and includes only the main parts of the system, so soon I decided to fetch a recent pkgsrc.tar.gz and installed some packages (I cannot live without perl ;-). The only thing I’m still missing about pkgsrc is a tool like portupgrade; it is currently been worked on, and e.g. pkg_hack does nearly everything portupgrade does, but you know It doesn’t work unless it’s right :-).

After all, I think the main reason for using NetBSD is its clean design and clean development model. The problem with the mainstream operating systems such as GNU/Linux is that people always want to have the newest features, and so the main goal is to get this features implemented as soon as possible, without the need to get a clean implementation. With NetBSD, you may get this features up to one or two years later, after somebody worked out a clean and stable implementation and a lot of testing was done on it. And when you get it, you can be sure, it works! So, now you know, why I prefer NetBSD, and what about you? Are you still loosing time getting your Linux distribution to work? Tired of fixing Debian package dependencies everytime you type apt-get dist-upgrade? So, maybe you should give NetBSD a try, but beware of making the wrong decision: NetBSD is no mainstream operating system, so if you always want to get the latest (unstable) features, you’re better off not choosing NetBSD to run your computer!

And last but not least: NetBSD rules :-).

Published: 2002-10-02 — Comments