Issue 4-26, June 30, 1999

Be Engineering Insights: Programmink Ze Quernelle, Part 3: Le Module

By Ficus Kirkpatrick

This article is about kernel modules (hereafter, "modules"). I will, for the sake of article autonomy, repeat some of what Mani said in his overview article, but if you haven't read it, you should. I command thee:

Be Engineering Insights: Kernel Programming on the BeOS: Part 1

As an example, we will use the until-recently-fictitious "xyz5038" module, which provides an interface to the still- fictitious XYZ Systems Model 5038 Squarepusher chip, a mainstay of many popular squarepushing peripherals. It's a simple chip, and has only two hardware registers, FOO and BAR. You can find the code for the "xyz5038" module at

<ftp://ftp.be.com/pub/samples/drivers/xyz5038.zip>

Using Modules

Modules export an API through a structure containing pointers to the functions the module provides, and any other ancillary information:

#define XYZ5038_MODULE_NAME     "generic/xyz5038/v1"

struct xyz5038_module_info {
    module_info        module;

    // returns contents of FOO
    int32 (*read_foo)();
    // returns contents of BAR
    int32 (*read_bar)();
    // returns previous contents of FOO
    int32 (*write_foo)(int32 new_value);
    // returns previous contents of BAR
    int32 (*write_bar)(int32 new_value);
};

In order to use these functions, all you have to do is ask the kernel for a pointer to this structure, and you're in business:

struct xyz5038_module_info *xyz5038 = NULL;

// get a pointer to the xyz5038 module
get_module(XYZ5038_MODULE_NAME,
          (module_info **)&xyz5038);
// read the value of FOO
foo = xyz5038->read_foo();

When you've no more use for the module, simply tell the kernel so:

put_module(XYZ5038_MODULE_NAME);

Your practical use of modules will be dependent on the functions exported by the ones you use, but that's all you need to get started using them.

Writing Modules

Creating your own module is a matter of extending the basic one defined in <module.h>. Note that the first field in xyz5038_module_info is a module_info:

struct module_info {
    const char* name;
    uint32      flags;
    status_t   (*std_ops);
}

The name field should be the name you provide in the header file for your module; in this case, XYZ5038_MODULE_NAME (or "generic/xyz5038/v1").

The flags field, surprisingly enough, is how you indicate which flags you want to be in effect for your module. B_KEEP_LOADED is currently the only flag there is.

The first time someone calls get_module() with your module's name, the kernel loads it. With every subsequent call, a reference count associated with your module is incremented. Every time someone calls put_module() with your module's name, that reference count is decremented, and when it reaches zero, your module is unloaded—unless you set B_KEEP_LOADED.

"std_ops" is pointer to a function you provide that deals with standard module operations. Currently, the only two things that entails are initialization and uninitialization. std_ops() usually looks like this:

static status_t
std_ops(int32 op, ...)
{
    switch(op) {
    case B_MODULE_INIT:
        module_init_hijinks();
        break;
    case B_MODULE_UNINIT:
        module_uninit_shenanigans();
        break;
    default:
        return B_ERROR;
    }
    return B_OK;
}

Exporting your module to the outside world is similar to publishing device driver hooks, but since you are the one defining the hooks, there are a few twists. You'll need to have a filled-out version of your module info struct:

static struct xyz5038_module_info
xyz5038_module = {
    // module_info for the kernel
    {
        XYZ5038_MODULE_NAME,
        0,
        std_ops
    },
    read_foo,
    read_bar,
    write_foo,
    write_bar
};

When loading your module, the kernel looks for a symbol called "modules", which contains a list of pointers to the modules you export, terminated by a NULL:

_EXPORT module_info *modules[] = {
    (module_info *)&xyz5038_module_info,
    NULL
};

Clever readers may have surmised by now that in the same process of including module_info to make your own module, APIs can be defined on top of that and then extended in other modules. As a matter of fact, this has already been done with bus managers, and it will be discussed in a future article.


Be Engineering Insights: Using a Test Harness to Build Ethernet Drivers

By Russ McMahon

The standard method for creating Ethernet device drivers is to write the entire driver and then use the protocol stack (NetServer) for testing and debugging. While there are samples and documentation to assist developers, this approach has some weaknesses:

To address these problems, I wrote the E-Drive application ("E" for Ethernet, and "Drive" for pushing or controlling) to help create and test a fully working Ethernet driver. The code is available from <ftp://ftp.be.com/pub/samples/drivers /E-Drive.zip>. I want to add more features to this, so please see the TODO file and contact me for updates or to offer suggestions.

In development, the first real packets to go through a new driver are typically the ubiquitous "pings." Ping is often used as a simple network diagnostic tool, and the Internet Control Message Protocol (ICMP) protocol supports sequence numbers and variable payload lengths, so it was a natural choice to build into E-Drive. Of course, the driver only sees an array of bytes, but those bytes make up ICMP echo messages. E-Drive also has limited Address Resolution Protocol (ARP) support. ARP allows other workstations on the network to resolve the address of your device while you're sending out ping requests. E-Drive works with the BeOS, Linux, and FreeBSD as both the sender and receiver of ICMP echo packets.

E-Drive is written like a normal Be application, with one menu and a text view for the main window. Choose File->Open Receive to load the driver and start reading incoming packets. File-> Transmit sends out ping requests. E-Drive dumps raw packets, debug information, and statistics to the text view (instead of standard out). There is also a settings panel for user preferences and network "must haves" like source and destination IP addresses. The values are saved in an E-Drive preference file.

The guts of E-Drive application are in the I0 class. The Open method opens the driver and gets the card's hardware address. By default, the driver must be in the /boot/beos/system/add-ons/kernel/drivers/bin folder with a link in /boot/beos/system /add-ons/kernel/drivers/dev/net folder. As a quick check that the kernel has located and loaded the driver, look in /dev/net for the driver's published name. If it's not there, E-Drive won't be able to open the driver.

Once the driver is open, a reader thread is spawned from the IOCtrl() method. The thread loops continuously and receives incoming packets. If the packet is an ARP or Ping request, the PrepareArpReply() and PreparePingReply() methods build up an appropriate reply packet. Both methods do some address swapping, set a few bytes, and calculate an IP checksum. E-Drive then replies to the requesting host. If the packet is a ping reply to a request sent from E-Drive, the ProcessPingReply() method is called. The ProcessPingReply() does some simple round trip calculations and displays the results. Other packets are ignored, although they can be viewed by the DumpPacket() method.

For sending packets, a transmitter thread is spawned from the IOCtrl() method when the user chooses File->Transmit from the main menu. The thread loops continuously and calls PreparePingRequest() at set time intervals to build up ping request packets. Each time PreparePingRequest() is called, the sequence number is incremented and set in the ICMP header. For the ICMP identifier, E-Drive uses the transmitter thread ID. The thread then sends the packet.

Using a test harness like E-Drive lets you focus on writing kernel code to manipulate a device without having to worry about the protocol layers above. And that lets you develop and test Ethernet device drivers with more confidence and control.


Developers' Workshop: Copying Files

By Christopher Tate

Several Gentle Readers have commented that perhaps we should call this column "Media Kit Workshop" rather than "Developer's Workshop." There's some truth in that—we have been emphasizing the glories of the R4.5 Media Kit over the last few months. However, we know that not every application needs to fling video clips around the screen. So this week I'll leave the rarefied world of real-time media handling and begin a series of articles on a more mundane—but necessary—topic: copying files.

"Bah!" you say. "Copying files is easy! Read the data, write it out again, and you're done!" Ahhh, but the Be File System (bfs) is a bit more complex than the run-of-the-mill flat Unix-style file system, and with power comes responsibility. Attribute data is important, too—some files, such as Net Positive bookmarks, consist solely of attribute data -- so a file-copying routine for bfs will be more sophisticated than one for a flat-data file system like ext2 or FAT.

You'll find a simple file-copying function and a simple program which uses at <ftp://ftp.be.com/pub/samples/storage_kit/CopyFile.zip>.

When I say "simple," I mean it. No bells, no whistles, just the ability to copy one file to another. Here's a quick look at the copy routine's salient points.

First, the prototype:

status_t CopyFile(const entry_ref& source,
    const entry_ref& dest,
    void* buffer = NULL, size_t bufferSize = 0);

Notice that instead of taking the source and destination files' paths as arguments, the function takes entry_refs. entry_refs are the standard BeOS representation of a file system entity (that is, a file or a directory). They're easier to manage than character string paths: no messy string manipulations or foreign character sets to deal with. Most of the Storage Kit uses entry_refs as the basic token for indicating a file.

entry_refs can be inconvenient when dealing with user input, as users tend to think in terms of file names and paths, but the Storage Kit provides a couple of ways to convert from a path to an entry_ref. The first is the BEntry class, which encapsulates an entry_ref inside a useful object wrapper. One of its constructors takes a path as an argument; once it is constructed, the entry_ref struct can be extracted via the BEntry::GetRef() method. The second alternative is a C function called get_ref_for_path() that converts from a character string path directly to an entry_ref. The sample "copyall" command line application in today's code illustrates both of these mechanisms.

The buffer and bufferSize arguments to CopyFile() are optional; they allow the caller to specify a buffer to be used when copying the file's data. If they are unspecified, the function allocates its own buffer and deallocates it when it's finished, so unless you're concerned with memory usage you generally won't bother to supply a buffer.

We all know how to copy *data* from one file to another, right? To conserve newsletter space I'll just skip to the interesting part: copying attributes from one file to another.

The attributes of a file are accessed by name. The BNode class provides access to a file's attributes via its GetNextAttrName() method, which iteratively returns the name of every attribute associated with the file. Copying the attributes involves successively reading each attribute of the source file and replicating it in the destination file.

There's some subtlety involved in guaranteeing that while you're reading the list of attributes, no other program is adding new ones to the file, or deleting old ones. To circumvent this unhappy race condition, the BNode class provides a locking mechanism. While the BNode is locked, the lock holder has the exclusive ability to access the file. Once the attributes are copied, of course, it's considered polite (to put it mildly) to unlock the BNode again.

In all, CopyFile() is a pretty simple routine. For now, it will serve as an introduction to file data and attribute manipulation, and give developers a commonly needed tool. In my next article, I'll dig a little deeper into the subject of copying files and discuss how to determine ahead of time whether a given file will fit on the destination volume. Estimating the amount of physical disk space required to store a file under bfs is tricky, as we shall see....

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.