This week and in my next article, I'm going to show you, function by function, how to write a BeOS driver for a USB Video camera—or at least how I wrote one. Before you get too excited, let me say that the cameras this driver supports are getting a bit long in the tooth, and may be hard to find in retail stores these days. Also, this driver requires support for isochronous transactions in the USB stack, so it will only work with the upcoming BeOS 5 release.
Vision VLSI Ltd. http://www.vvl.co.uk makes their Colour Processor Interface ASIC (CPiA) documentation available for those who want to write drivers for it. This documentation has been used to create a CpiA driver for Linux; links to the docs we'll use to write the BeOS driver are at http://webcam.sourceforge.net/#info. The most important document is the Software Developer's Guide for CPiA Cameras http://webcam.sourceforge.net/docs/developer.pdf. You can also find a list of supported cameras at http://webcam.sourceforge.net/#cams. I'm using the Ezonics EZCam USB (not the Pro or Plus version), which I picked up at a local store about a month ago. I'm not sure about the availability of the other cameras. Vision VLSI Ltd. is still around, still making ASICs that appear to use some version of the CPiA command set, so there may be some new cameras this driver could support with little or no modification. We'll see.
In this first article, I'll discuss implementing basic driver functionality, and how to send CPiA commands to the camera so its various parameters and modes can be set. I'll cover some material that's been covered before, because hearing the same thing two different ways can be helpful. To quote our esteemed QA Manager http://www.catastropherecords.com/StThomas.mp3, "This is the long boring part." In my next article, I'll tackle getting and processing video data from the camera. You can find the code for this week's incarnation of the driver at <ftp://ftp.be.com/pub/samples/drivers/CPiA.zip>. Grab it and follow along.
The driver comprises three source files: CPiA.c
contains all function
implementations; CPiA.h
contains definitions the public clients of the
driver will require, such as opcode constants and a parameter struct for
ioctl()
; and CPiA_priv.h
contains typedefs and constants used privately
by CPiA.c
. Since there's only one source file, it's not strictly
necessary for CPiA_priv.h
to be a separate file, but it keeps things
organized.
All BeOS drivers—USB or not—must export certain functions and values so the kernel can load and interact with them. I'll refer to these as the exported symbols. They've been discussed several times before, but let's review them in the context of USB drivers and the CPiA driver in particular.
The first exported symbol is the easiest to implement: it's the int32
value api_version
, and a driver simply needs to include the line
int32api_version
=B_CUR_DRIVER_API_VERSION
;
to fulfill its obligation. B_CUR_DRIVER_API_VERSION
is defined in
Drivers.h
, and as you might guess, it lets the kernel know which
version of the driver API a driver uses.
init_hardware()
is called only once in a particular runtime session
(i.e., from boot to shutdown). For devices that remain connected to the
computer for an entire runtime session—such as ISA and PCI cards—it
may make sense to use init_hardware()
to see if the device is indeed
connected and put it into a known initial state. However, it's not too
useful in drivers for devices that can be hot-plugged while BeOS is
running— such as USB devices—because the device may not be plugged
until after the first time the kernel loads its driver. Therefore, if
you're one of those short attention span types who's been looking for
init_hardware()
in CPiA.c
,
stop—it's not there. Implementing it is
optional, and I chose not to for this driver.
init_driver()
, on the other hand, is called every time a driver is loaded
by the kernel, and must be implemented by all drivers. init_driver()
should allocate resources that will be needed as long as the driver
remains loaded. USB drivers should take this opportunity to introduce
themselves to the USB (Bus) Manager like so:
/* declare these globally */ static char*usb_name
=B_USB_MODULE_NAME
; static usb_module_info *usb
; usb_support_descriptorsupported_devices
[] = { 0, 0, 0,VENDOR_ID
,PRODUCT_ID
} }; ... /* do this in init_driver() */ if (get_module
(usb_name
,(module_info**)&usb
) !=B_OK
) {dprintf
(ID "cannot get module \"%s\"\n",usb_name
); returnB_ERROR
; }dprintf
(ID "usb module at %p\n",usb
);usb
->register_driver
(DEVICE_NAME
,supported_devices
, 1,NULL
);usb
->install_notify
(DEVICE_NAME
, ¬ify_hooks
);
(Okay, you don't have to use dprintf()
so gratuitously, but I like to. To
review, dprintf()
works just like good 'ol printf()
, but it spews its
bits to COM 1 so you can catch them on another computer using a null
modem cable and a terminal program set to 19200 baud, 8 data bits, no
parity, 1 stop bit, and no flow control. This is serial debugging, and
it's pretty much indispensable for driver development. You can turn on
serial debugging by hitting the delete key as soon as the BeOS boot
screen comes up, or during runtime by hitting
alt+printscreen+d to jump
into the kernel debugger, then typing c to continue.)
The code above gets a pointer to the USB Manager module by calling
get_module()
with the name B_USB_MODULE_NAME
. Then it calls the USB
Manager's register_driver()
function to tell the Manager which devices it
would like to receive notifications about. For CPiA, I specify the vendor
and product fields of the usb_support_descriptor explicitly; for more
generic drivers, you can specify something other than the wildcard 0 for
dev_class
, dev_subclass
,
and/or dev_protocol
fields. How can you find out
what the vendor and product IDs of some random USB device are? It's as
simple as plugging the device in with serial debugging turned on. Among
lots of other useful output, you'll see a line like
vendor/product id: 0x0553 / 0x0002
Next, the USB Manager's install_notify()
function is called. This
provides addresses of the hook functions that will be called when various
events occur on the USB bus. Currently only two hooks are defined:
typedef struct usb_notify_hooks { status_t (*device_added
)(constusb_device *device
, void **cookie
); status_t (*device_removed
)(void *cookie
); } usb_notify_hooks;
As you may have guessed, device_added()
is called whenever a device
matching the usb_support_descriptor provided in register_driver()
is
added to the USB, and device_removed()
is called when it is removed. You
may not have guessed that device_added()
is also called after you call
install_notify()
, and device_removed()
is called after you call
uninstall_notify()
, if the device is connected at those times.
Lastly, CPiA's implementation of init_driver()
creates a semaphore to
synchronize access to the information about the currently connected
camera (remember that multiple threads can be executing a driver's code,
so protect that global data!). The semaphore name refers to a "table" of
devices, but currently the CPiA driver only supports one camera as a
simplification. However, next week's version may support more than one
camera, and some code in this version is designed for that possibility.
The number of devices a driver may support has no artificial limit in
BeOS, but there are some practical concerns specific to each device that
should be considered. For example, USB bandwidth is theoretically 12Mbps,
so supporting two 10Mbps USB devices may not work out well.
The kernel calls publish_devices()
after init_driver()
to get the names
by which the driver wishes to be known in the
/dev
hierarchy. CPiA is a
USB video device, so it returns video/usb/CPiA/0
from this function if
there is a device connected at the time it's called. If the driver
supported more than one device, it would be published as
/dev/video/usb/CPiA/0
, /dev/video/usb/CPiA/1
, and so on. Furthermore, a
single driver can publish multiple logical devices of different
functionality if the physical device has such capabilities. For example,
many sound cards also have a MIDI port; a driver for such a card might
publish devices in both /dev/audio/
and /dev/midi/
.
Before the dev_names string array is filled out, though, it is freed if
it remains filled out from a previous call. The only safe times to free
this array are in publish_devices()
after the first time it is called,
and in uninit_driver()
. At any other time the array may still be in use.
find_device()
is called by the kernel when a potential client calls the
POSIX function open()
on a driver, so the driver can provide its
implementation of open()
and its friends
close()
, ioctl()
, read()
write()
, etc. It does this by returning a device_hooks structure filled
out with the addresses of its implementations of those functions. If a
device supports multiple logical devices of different functionality as
described above, it should check the argument of find_device()
to see
which logical device is being opened, and provide the corresponding set
of hooks. More on the implementation of these hook functions in my next
article.
uninit_driver()
is called just before a driver is unloaded by the kernel.
It's the last chance to free resources allocated in init_driver()
and
elsewhere. In the CPiA implementation, the dev_names
string array used in
publish_devices()
is freed, the USB Manager function
uninstall_notify()
is called to inform the Manager that the driver will no longer be
requiring notifications, the dev_table_lock
semaphore is recycled, and --
lastly—the "connection" to the USB Manager is severed. At this point,
that certain lady has finished singing for the CPiA driver.
As introduced in init_driver()
above, USB drivers must implement a set of
hook functions that are called when certain events occur on the USB.
The main tasks to accomplish in device_added()
are creating and
initializing a cookie that contains device state information and
resources, and verifying that the device has the capabilities we expect
and that it is working properly.
The CPiA device cookie is defined in
CPiA_priv.h
and looks like so:
typedef struct device device; struct device { usb_device*dev
; /* opaque handle */ boolconnected
; /* is the device plugged into the ** USB? */ int32open_count
; /* number of clients of the ** device */ int32num
; /* instance number of the device */ ucharvers
[4]; /* data returned from ** GetCPIAVersion command */ sem_idlock
; /* synchronize access to the device */ sem_idcmd_complete
; /* available on completion ** of CPiA command */ area_idbuf_id
; /* ID of buffer for data returned ** from CPiA command */ void*buf
; /* data returned from CPiA command */ size_tactual_len
; /* length of data returned by ** CPiA command */ status_tcmd_status
; /* result of command */ /* temp */ usb_pipe*video_in
; uint16max_packet_size
; };
The comments should give you a rudimentary idea what each field is used for. Perhaps the most important field is dev, which is the handle to the USB stack's notion of the device. It's opaque to clients of the stack, but must be passed to any USB Manager functions that need to know which device you'd like to operate on. The fields after /* temp */ will most likely be moved to another cookie next week.
For CPiA, I've broken out the cookie creation and initialization process
into two additional functions. add_device()
allocates memory for a device
structure and adds it to the device "table," which is currently a single
pointer named connected_device. Again, only one connected device is
supported right now for simplicity. add_device()
in turn calls
init_device()
, which sets the fields to sane values and allocates the
resources they represent.
If add_device()
succeeds, device_added()
's cookie parameter is set to
point to the newly created device structure. This pointer is passed to
the device_removed()
USB notification hook so its resources can be
properly reclaimed. It will also be passed to each of the device hooks
described below. Any device-specific data that needs to persist between
USB notification hook calls and device hook calls must live in the cookie.
After successfully setting up the cookie, the CPiA driver issues two CPiA
commands to the camera, to get its version information and to check it
for any errors that may have been discovered during its Power On Self
Test (POST). The workings of issue_cpia_command()
will be discussed in
the next section.
Vision VLSI thoughtfully gave CPiA commands the same format as USB SETUP
packets, which means it's a cinch to issue them to the camera using the
USB Manager's queue_request()
function.
First some terminology. USB is completely host-centric and host-controlled, where "host" means the USB controller to which USB devices are connected. All movement of data is described with reference to the host. OUT always means movement from the host to a device on the bus; IN always means movement from a device to the host. Devices can't initiate data transfer on their own, nor communicate directly with each other. All data transfer is initiated by the host.
There are four types of USB data transfers: bulk, interrupt, isochronous, and control. All USB devices support control transfers and have an endpoint (a data sink and/or source) dedicated to them. A control transfer begins with the host sending an 8-byte SETUP packet to the device. The device may then respond with a DATA IN packet.
As documented in the Software Developer's Guide for CPiA Cameras, each CPiA command consists of 8 bytes in the SETUP packet format. The first byte indicates the type of command (whether or not a DATA IN packet will follow the SETUP packet). The second byte uniquely identifies the CPiA command. The next 4 bytes can be used individually or in various aggregations as parameters to the command. The last 2 bytes give the size of the following DATA IN packet, if any.
The function issue_cpia_command()
in
CPiA.c
makes it convenient to issue
a command exactly as it is presented in the Developer's Guide, because it
takes each of the 8 bytes of the command as a separate parameter. It's
really just a wrapper for the USB Manager's queue_request()
function.
queue_request()
takes the same 8 bytes as parameters, although it
slightly complicates matters by combining the latter 6 bytes into 3
shorts.
The "queue" in its name is a clue that queue_request()
operates
asynchronously; consequently, it also takes as a parameter a function to
be called when the transaction is complete. To wait for that completion
before returning, issue_cpia_command()
immediately tries to acquire a
semaphore in the device cookie that has been initialized to 0—thus the
call to acquire_sem()
blocks. That semaphore is only released in the
completion routine cpia_command_callback()
, after the requested data (if
any) has been copied into the cookie's buffer.
The strategy of using a zero-initialized semaphore to wait for completion
of an asynchronous function will come in handy again next week when we
start streaming video data from the camera. The CPiA uses isochronous
transfers to do that, and the USB manager has a queue_isochronous()
function we'll use to fill our buffers with pixels.
In addition to the exported symbols, all BeOS drivers must also implement
certain functions that provide the functionality of the familiar POSIX
functions open()
, close()
,
ioctl()
, read()
, write()
, etc. I'll call these
functions the device hook functions. As they are accessed through the
function pointers in the device_hooks structure returned by
find_device()
, you can name them anything you want (unlike the exported
functions), but it is customary to name them <logical device>_<hook
name>. For example, the hook functions for the MIDI part of a sound card
driver might be named midi_open()
, midi_write()
, etc. Since CPiA only
publishes one kind of logical device, I'll just use device_open()
, etc.
Our time's almost up for this article, however, so we'll discuss the implementation of the device hook functions in my next article.
The CPiA driver is not very exciting yet. Well, there's a bit more
implemented in the sample code than what I've covered so far, including the
device hooks. Feel free to explore those, especially device_control()
,
where you may find some more interesting stuff. The file test.cpp
also
contains the beginnings of a basic test harness we'll use to exercise the
driver. 'Til next time...
After Microsoft's announcement, last week, I received mail regarding the availability of BeOS on the X-Box. As one correspondent noted, it would be good, mischievous fun to imagine the effect of BeOS—a true media OS -- on a dream media platform such as the X-Box. The suggestion gives me an opportunity to clarify our position, to restate what our focus really is.
First, though, let's see what we think we know about the X-Box: 600 Mhz Pentium, 64 Mb of RAM, 8 Gb for the hard disk, DVD player, high-speed rendering chip from nVidia, 3D audio processor, and I/O for Ethernet, game controllers, and the like. The X-Box site makes a generic reference to an x-86 processor, but Intel appears to have won the last round in the beauty contest (it would be interesting to know the quid pro quo).
As for the software engine, the Wall Street Journal, in its account of the "rebel" project inside Microsoft, refers to "the core of Windows 2000," or words to that effect. The price wasn't specified, but rumors peg it around $300, or less. Game developers will have more details soon, and the product is slated to be available in about 18 months, in time for the 2001 Xmas season.
At first glance, it looks more like a PC than a game console. Everything comes from the PC-clone organ bank: the hardware, the software, and this being Microsoft, the world view. This leads some critics to predict failure, for cultural reasons. One local wag calls the X-Box Game Bob; others say that 18 months is a long time, the X-Box is too big, too expensive.
By contrast they say that the Play Station is a dedicated machine, not a tricked-up PC. This rehashes an old argument about the advantages of dedicated hardware versus the benefits of the PC-clone organ bank. The latter might not always be the prettiest, but the volume and competition in the PC world make the ecosystem brilliantly efficient. On the other hand, on a game console, the real CPU is the graphics engine and the "classical" microprocessor is more an ancillary unit passing display lists to the graphics chip, an ASIC doing the "real work" of high-speed rendering.
Back to the question. The X-Box looks pretty close to a PC and promises exciting multimedia hardware, so isn't it a "natural" for BeOS? For argument's sake, let's explore the "how" of this. First, we would need Microsoft's blessing to get the hardware specs, when available. That's not likely—the blessing, that is, not the availability. This would put us in the position of doing some kind of reverse- engineering. No thanks. Life is fun in the Internet Appliance space, where Microsoft doesn't control much of anything—at least for now, so let's enjoy it.
Of course, one could speculate about Microsoft's motive with this announcement. Is it offensive strategy to conquer territory, or is it a defensive maneuver against Sony, Nintendo, and Sega invading the home and becoming players in Internet access? I'd bet on the latter. By 2001, game consoles will all offer high-speed, always-on Internet access; there will be lots of content to view and sell. This would further diminish Microsoft's efforts to establish MSN as a premier portal for the Internet. Conversely, a Microsoft game console with high-speed Internet access is a great vehicle for MSN, for attracting gamers, multiplayer games, selling music—to name but a few possibilities.
I also noticed another line of speculation. Who will hack into the X-Box, write drivers, and make it run Windows and Windows applications? I didn't ask why, but who. In any case, it will be interesting to watch what happens to the X-Box. Will it be CE'd by Sony, Nintendo, and others? Will it really give rise to a new industry segment? Or is it just another paper tiger like the Zero Administration PC, the Net-PC, and the Simple PC?