Issue 5-11, March 15, 2000

Developers' Workshop: Writing a USB Video Camera Driver, Part 1

By Todd Thomas

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.

Exported Symbols

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.

api_version

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

int32 api_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()

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()

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_descriptor supported_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);
    return B_ERROR;
}
dprintf(ID "usb module at %p\n", usb);

usb->register_driver(DEVICE_NAME, supported_devices, 1,
    NULL);
usb->install_notify(DEVICE_NAME, &notify_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.

publish_devices()

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()

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()

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.

USB Notification Hooks

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.

device_added()

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 */
    bool     connected; /* is the device plugged into the
                         ** USB? */
    int32     open_count; /* number of clients of the
                         ** device */
    int32     num; /* instance number of the device */
    uchar     vers[4]; /* data returned from
                         ** GetCPIAVersion command */
    sem_id     lock; /* synchronize access to the device */
    sem_id     cmd_complete; /* available on completion
                         ** of CPiA command */
    area_id     buf_id; /* ID of buffer for data returned
                         ** from CPiA command */
    void*         buf; /* data returned from CPiA command */
    size_t         actual_len; /* length of data returned by
                         ** CPiA command */
    status_t     cmd_status; /* result of command */
    /* temp */
    usb_pipe*     video_in;
    uint16     max_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.

device_removed()

device_removed() marks the device as being removed. If the device's ref count (dev->open_count) is zero, no clients have the device open, so its cookie is freed by calling remove_device(), which calls deinit_device() to free the cookie's dynamically allocated resources.

Issuing CPiA Commands

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.

Device Hooks

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.

So What?

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...


What About the X-Box?

By Jean-Louis Gassée

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?

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