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