|
Be
Driven
|
Device Drivers in the Be Os |
|
|
|
|
|
Modules |
|
Credits
Large Sections from
BeNewsletter, Volume III, Issue 21; May, 1999
BE ENGINEERING INSIGHTS: Kernel Programming on the BeOS: Part 1
By Mani Varadarajan mani@be.com
and
BE ENGINEERING INSIGHTS:
Programmink Ze Quernelle, Part 3: Le Module
By Ficus Kirkpatrick -- <ficus@be.com>
What is a module?
Modules are like add-ons that export an API for use by other modules
or drivers, all within kernel space. Modules, are kernel-only because
they provide functionality for drivers and other modules
In this way, they behave like a Special Type of Device Driver used
in the same way one might use a Shared Library. This means you can
not load a Device Driver using load_add_on, and you can not load a
Add-on using get_module().
Modules must be compiled and linked as a Device Driver.
get_module allows device drivers to load modules for there own ends.
This is how Bus Managers, Config Managers, PCI Managers, and other
such gems are called.
Modules present a uniform API for use by other modules or drivers.
This is a useful way of separating out commonly used functionality
from a driver.
Take the example of a SCSI device driver talking to a SCSI device.
The device hangs off a SCSI bus, which in turn may be one of many
busses on the system. All SCSI devices speak a common command set
that is independent of the controller used to send the commands. Rather
than have each SCSI driver (SCSI disk, SCSI CD, SCSI scanner, etc.)
know how to deal with each of the possible types of SCSI cards, it
would be nice if there were a generic interface to a SCSI card for
each driver to use. This is accomplished by having one module implement
each SCSI card, which then presents a generic API.
These modules are further managed by a SCSI "bus manager"
module, which knows how to deal with multiple busses and present them
in an encapsulated format to each driver. The bus manager's API is
what the driver has to deal with, reducing its complexity a great
deal. We also have USB, IDE and PCMCIA bus managers.
Another example of the use of the module architecture is a sound
driver which publishes a MIDI device. MIDI functionality can be encapsulated
in a module so that all sound drivers can access it, avoiding duplication
in each driver.
What a module is not
Even though a module is compiled as a Device Driver, it does not
export the same functions as our normal File type device driver.
In this way, the mechaism to load Device Drivers, and Modules is
different.
Using modules
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>
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.
A Guess at Busses
[ Written Pre Any Doco Release on Modules :
I am not going to rewrite it as the info looks like it will be forth
comming]
I have not had time to play mapping parameters to it, or using
a debugger to read through the assembler in the kernel get_module()
to find the specification, but at a guess, it returns the base of
an object inherited of module_info (in there peculiar way mentioned
below).
I would say that this is similar to the mechanism used to load
device drivers by the BeOs Kernel, but I would at a guess say that
the kernel uses the "load_kernel_addon()" function that
we don't have prototypes for either.
Given the above info, it should also mean that you should be able
to create your own devious bus drivers, for other device drives
to 'add-on' or should i say, get_module!
This is what they are talking about in the newsletters when it
comes to, New Busses like SCSI, IDE, etc..
Here goes our Reverse Engineering attempt at the story.In the beginning
there was the base most module definition.
<be/drivers/module.h>
"
/* module flags */
#define B_KEEP_LOADED 0x00000001
/* module info structure */
typedef struct module_info module_info;
struct module_info {
const char *name;
uint32 flags;
status_t (*std_ops)(int32); <<-- What is this???
};
#define B_MODULE_INIT 1 <<-- Why?
#define B_MODULE_UNINIT 2 <<-- Why?
_IMPEXP_KERNEL status_t get_module(const char *path, module_info **vec);
_IMPEXP_KERNEL status_t put_module(const char *path);
"
Then We Have...
<be/drivers/bus_manager.h>
"
typedef struct bus_manager_info bus_manager_info;
struct bus_manager_info {
module_info minfo; <<--- NOTE THIS
status_t (*rescan)();
};
"
Then We Also Have...
<be/drivers/config_manager.h>
"
typedef struct config_manager_for_driver_module_info {
module_info minfo; <<--- NOTE AGAIN
status_t (*get_next_device_info)(bus_type bus, uint64 *cookie,
struct device_info *info, uint32 len);
status_t (*get_device_info_for)(uint64 device,
struct device_info *info, uint32 len);
status_t (*get_size_of_current_configuration_for)(uint64 device);
status_t (*get_current_configuration_for)(uint64 device,
struct device_configuration *config, uint32 len);
status_t (*get_size_of_possible_configurations_for)(uint64 device);
status_t (*get_possible_configurations_for)(
uint64 device,
struct possible_device_configurations *possible,
uint32 len);
/* helper routines for drivers */
status_t (*count_resource_descriptors_of_type)(
const struct device_configuration *config,
resource_type type);
status_t (*get_nth_resource_descriptor_of_type)(
const struct device_configuration *config, uint32 n,
resource_type type, resource_descriptor *descriptor,
uint32 len);
}
config_manager_for_driver_module_info;
quot;
Can you see a pattern emerging?
So, we have in effect a very primitive form of inheritence taking
place.
Each layer, inherits of the other, by placing it parent as the first
instance in its data structure. The functions, are the functions for
each class, minus the constructors, deconstructors, or any mechanism
for virtual overloading!
But you should get the picture by now..
For Instance, with anything Inheriting of bus_manager, we could collect
all the instances at a central point, and just call the rescan functions.
For an example look at the "DiskSetup" utility provided
with Be... Rescan()... Hmm. ;-)
Now, if you look at the PCI and ISA busses, they also inherit of
bus_manager. So, to create a bus_manager, we at least have the first
steps of how to layout our object.
The Communal Be Documentation Site
1999 - bedriven.miffy.org |