Table of Contents
BE ENGINEERING INSIGHTS: Programmink Ze Quernelle, Part 3: Le Module By Ficus Kirkpatrick ficus@be.com
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: <http://www.be.com/aboutbe/benewsletter/volume_III/Issue21.html> 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: 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. I'd like to tell a joke to end my article with something funny, but all the ones I know would probably be removed during editing. [Editor's note: Not necessarily.] Sorry. BE ENGINEERING INSIGHTS: Using a Test Harness to Build Ethernet Drivers By Russ McMahon russ@be.com
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 ctate@be.com "Developers' Workshop" is a weekly feature that provides
answers to our developers' questions, or topic requests.
To submit a question, visit
http://www.be.com/developers/suggestion_box.html.
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:
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....
1997 Be Newsletters | 1995 & 1996 Be Newsletters Copyright © 1999 by Be, Inc. All rights reserved. Legal information (includes icon usage info). Comments, questions, or confessions about our site? Please write the Webmaster. |