Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2005-509
$eventId
ID of event: wwdc2005
$eventContentId
ID of session without event part: 509
$eventShortId
Shortened ID of event: wwdc05
$year
Year of session: 2005
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC05 • Session 509

Creating Drivers for USB Devices

OS Foundations • 1:03:22

Learn how easy it is to access a USB device as Apple's engineers create drivers for USB devices that are provided to them by the audience at the start of the session. They'll demonstrate how to access devices from applications, as well as how to debug USB drivers using the logging version of the IOUSBFamily KEXT.

Speaker: Fernando Urbina

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it may have transcription errors.

Welcome to our USB session for WWDC. My name is Fernando Urbina, and I'm part of the USB team here at Apple. This year we decided to do something a little bit different than what we've done in the past. In previous years we've just come and given you slide after slide after slide of dry code and stuff that we've been doing that was exciting for us but maybe not that hands on for you. And in the spirit of the whole WWDC session this year, we are going to, for the most part of the presentation today, go through some of our sample code and use the demo machines to show you how to get at your devices, how to access, how to start some reads, and hopefully this will give you more of an idea on how to start your USB drivers. Before doing that though, I thought that it would be nice to highlight a couple of the issues that we've had or the highlights for Tiger.

If you've been with us for the last few years, you probably have seen this diagram, which is part of the common class specification, and it shows what our matching criteria, and in this case, for USB devices is. And this is just a way that we rank drivers, and so if you specify the different fields of the device descriptor in your matching dictionary if you're in user space, or in your personality for kernel extensions, depending on what fields are there, we rank your drivers and we load those drivers. And this hasn't changed at all.

However, with Tiger, we have wildcard matching. This has been a request over the years, and it sort of never got up in the priority queue in our stuff. And finally, for Tiger, we did it. And what is wildcard matching? Well, some of you developers have a need to support multiple devices from the same vendor. And the way to do that is, if you're in the kernel, you have to create a personality for each device, cut and paste, and you can have a lot of personality depending on how many devices you have. The personalities are probably just the same except for the USB product ID, but it's cut and paste, and it's error-prone. So it's not very nice when a new device comes from the same vendor, then you have to go and update your text, or if you're in your user space, you have to change your code to look for that new product ID. So what wildcard matching allows you to do, and it's no big surprise, is that you can specify a wildcard for one or more of those fields. When you do use a wildcard, we decrease the probe score so that a driver that fully specifies all the fields will still win in the driver matching than yours that has a wildcard. But it allows you to have it. And we also have what I call the partial wildcard. And this is a little bit more esoteric, but it allows a product ID to have a mask.

For example, at Apple, we have divided our product IDs in into different groups depending on what section of the company is developing the product. And so you could have your driver match to all the product IDs that start with 8, 3, and then the last two will be wildcards.

So again, this is the matching criteria for a device driver. And if you specify the vendor and the product ID, you have a vendor-specific driver. In order to make it wildcard, all you do is create a, instead of putting a number for the product ID in this case, you put a star. Later in the demo, I'm gonna actually use that and show you how it all works.

One more slide that I wanted to put in was, it's nothing new, but it was a highlight of an issue that affects you as developers, but should not affect your users. And that is the bus topology in USB, especially as related to full speed devices that are attached to a high speed hub.

When you have that situation, a full speed device attached to a high speed hub, USB works in what is called split transaction mode and that means that the high speed controller talks to the high speed hub with the high speed protocol and the high speed hub then goes and translates those high speed commands into full speed or low speed commands, traffic. As a user, this should just work. In reality, it just works. However, it's a lot more complicated in terms of the software. And the idea of this slide is that when you have an issue with one of your users, and if they have connected your full or low-speed device into a high-speed hub, you should ask them to connect it to the root hub of the computer, directly to the computer, so that it acts as a full-speed device and there's no translation. And you can isolate things that way, of course. If there is a bug, then you go to bugreporter.apple.com and report it to us, and we try to figure out what's going on. When running in split transaction mode, um... One of the things that we did in 10.3.9 and in Tiger is we improved periodic transactions in this mode. Periodic transactions include not only ISOC in and out, but also interrupt transactions. And the whole bandwidth allocation for these split transactions has been revamped and the timing, especially for isochronous, has changed quite a bit. And in fact, our Apple USB audio driver had to change in order to satisfy these new timings.

And what changed was the fact that the isochronous out transactions would go out in 1039 and in Tiger towards the end of the one millisecond frame, Whereas in the past, it usually went out at the beginning. And you would think that that wouldn't matter too much, but especially in audio situations, it did. If you at some point use the Apple USB audio driver as a source for your own vendor-specific driver, I would encourage you to go and take a look at the source for 1039 or Tiger for that driver and see what changed because chances are that your driver won't work as well.

Final point there is that all our machines right now use the NEC controller for high speed. And that controller has two companion OHCI controllers. And that's why I said that when you plug it into the back of the machine, if it's a full or low speed device, you will get it connected directly to an OHCI controller. Interestingly enough, the developer transition systems have an EHCI controller with UHCI companion controllers. If you remember last year, we talked about having UHCI support in the I/O USB family. We've had a lot more work on it over the past year. And at least those systems now have it. It should be transparent. It works. You can always go, if you don't have a transition system, test with a PCI card that has UHCI companion controllers. Thank you. Okay, so now we come to the live debugging part of the session. I'm going to use a couple of devices to go through discovering the interfaces, setting up the matching dictionary.

finding the endpoints, getting the properties and reading from the endpoints. Then I'm also, at the end, gonna demonstrate, and this is very simple, but I think it's useful to at least some of you, how to do bulk reads, or how to do chain reads, rather, in order to improve the performance of your driver.

One thing to remember is that you can issue however many reads you want. The controller will execute them in order, and this allows you to work in situations where the latency might be high. At the extreme case, we could have a USB network server like the one from Keyspan that the USB transaction is actually going over the bus. Or sometimes the workload latency that we've talked about in the past will be high enough that if you only issue one read, then you're going to be starving your device and your performance will not be as high.

The first tool that we use, and I'm hoping that most of you are familiar with it, is the login version of the IO USB family. What is the login version? When we are developing before a release, we have all these logs, all these print tabs, essentially, in our family to let us know how things are going, and we use it to debug problems, etc., etc. Once we are close to GM, we strip all those logs out because they take space, they take wired memory, and wired memory in the kernel is very expensive. So shortly after a release, be it a software update or a major release, we go and we post the login version of the IUUSB family at that URL over here. And we encourage you to go and download that and install it and run prober as we'll see later. To debug not only our stuff, but to learn what we're doing, but also to use USB logs in your code.

One interesting bit is that this is also a way for you to get any new headers that we might have changed since the last major release. Software update, when there's a software update, it only updates the binaries in the system. It doesn't update any headers. So you have no other way to get those headers until there is a release of Xcode that installs all the headers in the system again. Or in our case, you can download our family, our package. You could also go for those projects that are open source and build them yourselves and install headers yourself, et cetera. But that's a little bit more of a pain.

Sometimes USB logs are not enough. I was in the session yesterday for FireWire, and that FireWire SDK 20 has the kprintf stuff. These are essentially printfs from your code that could go out the FireWire bus. And I've been using it lately to debug some enumeration issues during a startup when we don't have a USB log that we can take a look at. We've also used it in the past to debug sleep wake issues, because sometimes the network drivers go to sleep and they're not available to do too much in debugging at that point. So it is very handy if you need to debug something where the USB log is not available. So take a look at that SDK and download it. Now I'm going to go through USB Prover. Most of you are probably familiar with it, but I want to still go and show you a little bit about it.

I have a device here, it's a prototype device, and it actually had bad firmware that I contacted the manufacturer. USB prober has four panes. I'm gonna start here with the left mode, the bus probe pane. This... Gives you a listing and decoding of all the descriptors manually. We don't try to use, or we try to use the least amount of the USB stack in the kernel in order to get at them. So we don't use any of the properties that are in the IO registry, for example, for the product name. We go and we issue the device request to get that length and then the full device request. And for example, this device you can see right here that-- let me unhighlight it-- that we have written that the device did not respond to a request for the first four bytes of a descriptor.

Some devices that are only tested on Windows don't realize that they have this bug. And because Windows does things differently, we do it one way, they do it another way, both of them are compliant. But some devices will not respond when we ask for just four bytes of the config descriptor. We do this because the Length of the descriptor is in bytes three and four. And that's all the information we need in order to then issue a request for the full configuration descriptor. This device doesn't respond to it. However, we realize that and then we go ahead and we request the whole thing. USBProver will also show you the whole length, or sorry, the whole config descriptor in raw format. And then it goes and decodes all the different descriptors that it encounters.

Some of these descriptors, like the video descriptor and the audio descriptors, the specs are huge, and there's a gazillion formats, and sometimes, we might have bugs in there. So if you are an audio developer, video developer, and realize that we are not decoding and you're pretty sure, just submit a bug and we will fix it. You get bonus points if you fix the bug by going to Darwin and downloading USB Prover and just send us the diffs.

I like those kind of bugs. I was mentioning earlier about the config descriptor, like in the sense that bytes 3 and 4 are where the length byte is. And this reminded me about our transition to Intel-based Macs. We are sort of in a very good position in terms of USB because USB is a little-endian system, just like Intel. And the Mac is, obviously, the PowerPC is big-endian. So we already had to, when dealing with quantities on the USB bus, do the translation and use the byte ordering macros. Yes.

As far as you are concerned, if you are doing device requests and things at the low-level USB, you won't have to change anything. We use the macros that know what architecture we're running on, if it's little-endian or big-endian, and we will do the translation when appropriate. And the fact of the matter is that when our stack was being used in the development of these new machines, we didn't have any problems because we were already using the correct macros.

The next tab is just the kernel extensions and it shows you USB kernel extensions and this is just the output of kextad from the system. The I/O registry tab shows you two different things. One is the I/O USB plane. This is just the bus topology of the system, as you could see it probably in Apple System Profiler as well. But with a little more data here, you can click on a device and then hit Details. it will show you the IO registry items for that device. This does not show you any drivers, it does not show you any USB interfaces, anything like that. It is just the bus topology of the system. If you wanna see the other stuff, you go to the IO service plane. And you can disclose a device, for example, this USB camera. And you see here that the I/O USB composite driver loaded for the device even though it is not a composite device, but I have a personality that matches a miscellaneous class device to the composite driver. I didn't change the font for the details so it's hard to read, but here you could find the probe score for this driver and of course there's the different interfaces and these These are actually video control and video streaming interfaces. We do this all from user space, and so there are no kernel extensions matched to it. The final tab is USB logger, and this is where the USB logs from the system go. I have loaded the login version of the I/O USB family in this machine, and so I can look Ocará.

level six, let's do level five because it's not as verbose. Start it and then if I unplug the device we'll see all the stuff happening from the USB stack. One thing that I do that might be handy for you, and I always tell our testers to do, is to go to the bus probe window.

and uncheck the refresh automatically when you are getting USB logs. Because when you're doing plugs and unplugs and that checkbox is on, it will automatically try to, when it sees a new device, issue device requests to try to get the information for USB prober. And that might confuse you. It certainly sometimes confuses me when I'm looking at logs and I see stuff that I don't know who's doing it. So that's just one handy... in case you're looking at a USB log and you cannot understand what's going on. We could go to the slides again, please.

So that is what I talked about. One thing that I did not mention is that level 7 is really, really verbose. And I only use it for a very short amount of time when I want to know a lot of what happened. If you use it for long periods of time, you can overrun the buffer, and then you lose logs, and you don't know what happened.

Sometimes it comes to tool machine debugging, and if you're doing kernel extensions and you cannot get around that, I won't do it. We tried for like two or three years to do it on stage, and that was a bad idea. Yesterday there was a session by the I/O Kit team that was very useful in terms of doing kernel extensions. I mention it here in case you didn't attend, you can go when the slides come out or the DVD or whatever it is. And if you did not attend, I encourage you to look at it again. And sometimes you get panics and the only way to know what's going on is to actually debug the panic and figure out where in your code it's bailing out. If you're doing kernel extensions, you won't be able to get away from understanding panics and decoding panics.

Of course, user space, like we've said all along, is a lot better. It's much easier to debug. It's easier on your machine because you know, you don't keep panicking. Symbolic debugging is great. I, you know, over the last year, I've been developing the USB video class driver, which is a QuickTime digitizer. it's a lot better than debugging things in the kernel. So in my case, sometimes I have to modify the IOSB library, and I can build a debug version of it, install it live, step into it from my application, and it's all there.

It's a lot better. In some cases, you still need to use printfs, And in my case, I have this high priority threads running on the callbacks. There's other threads doing other stuff. And so stopping the program just disturbs the flow and you have to resort to print thefts. The new Xcode 2.1, I was on the session yesterday morning. and it allows you to create a breakpoint, print something, and continue so that it actually doesn't stop and so that's that's going to be really handy to use.

Finally, like I know if you've lived on the USB list, you've heard it a lot before is, sometimes you just need to know what's going on in the bus. This Q&A that I mentioned there has a little section on the different models of USB bus analyzers. They're all at different price points. Right here for our demo, I have a CATC mobile high speed, which is just a PC card that goes into a Windows laptop. But when you can't figure out things, you need to know what's going on in the bus. Did the controller actually put the transaction on the bus? Was it the right format that you were expecting? Did the device respond to that transaction in the manner that you thought it was? The only way to know the answers to these questions is to look at the bus. OK, now we're going to move to demo two, if you will, please. And-- excuse me a second.

We're going to plug in another device here. And we have an -- This little device from one of our developers from Code Mercenaries, and it's the IO Warrior 40. And we're going to use this to do what I mentioned earlier, look at the device, discover the interfaces, and start reading from it. So, of course, since I had it not refresh automatically, I have to go over here. And first of all, I just want to go and show you what the device has. This is just a device descriptor over there. The interesting... thing is the configuration descriptor. It only has one. There's two interfaces. They are both hit class interfaces.

This first one has an interrupt input endpoint, and alas, the second one also has another interrupt input endpoint. I'm going to use this demo monkey assistant that allows me to... to type things quickly so that you guys don't have to wait for me while I type. So I'm going to open one of my projects here. I don't like to work in this mode, so I'm gonna change the preferences to be... a condensed, and then I'm gonna open the project again.

This is our file. We go here to the main. It's very easy. First thing we're gonna do is just look for the device interfaces. And that is right up here. I'm not gonna go through every single line and show you one thing. But we know, or I know, from the common class specification, that in order to match to a USB interface, you have to specify, if you're doing a vendor specific dictionary, you have to specify the vendor product ID, B configuration value, and interface number. I have here the vendor ID and the product ID. I'm creating those numbers and setting them in our matching dictionary, but I have not done our configuration value and interface number.

This is the official font, so I don't want to mess with that. Sorry. Okay, I'll mess with it. Don't blame me. Okay. Editor fund. I didn't think that was it. But let's go to-- 128. No. Let's see. Did that do it? Let's see, is this any better? And I go back to-- is that any better? OK. Okay, sorry about that. As I said, we have the vendor ID and the product ID matching dictionary. Now, I'm going to add the configuration value and the interface number.

You can see that I am using now the wildcard matching criteria. And I don't have to create a number or anything. I just said the dictionary value with a value of the wildcard character right there. So what this is gonna do actually, it's not only gonna match to my first interface, but it's also gonna match to the second interface that we saw for this device. If we have multiple configurations, then it would match to all the interfaces in every configuration.

Then once we discover, we set the matching dictionary, we look for any I/O services that match that dictionary and get a device iterator. And with a device iterator, we can open the connection to the I/O USB interface object in the kernel. And I'm not gonna go through all of that. But once we have that object, we can go and work with that interface. So we'll go up there, and we see that right now it's not doing anything. But the first thing we're going to do is let's do a save. And so all I'm doing here is trying to open that interface. We'll build.

here, and hopefully it'll just go and build, and it built fine, and now I'm gonna run it and see what happens. Oh, dang. What it says here, and I'm sorry I couldn't figure out how to make this font bigger. I have some of the assert macros and I'll show you here.

Well, it's right here. It requires string. It says if the string is not IO return success, we jump to exit and then we print a string value. And I have this USB to error string. All this does is a function up here that will take an IO return and actually return a string with the error name. And that is very handy after a while. I don't know what the 2C5 or 2C7 or which one is which. So going back to the run, what it says here is debug assert WWC demo KR equals equals KIO return success exit KIO return exclusive error.

Ah, we go to Prober. We can go to the IO registry window, look for our device, which is right here, look at the interface, and we see that the IO USB hit driver has matched to it. And that is because it's a hit device and the kernel driver matches to it before anybody in user space can use it.

Of course, at this point, if this was a real thing, You can use the head manager to actually get at the raw data coming from the interrupt pipe and interface to that device using an IOU USB header interface instead of an IOU USB interface interface. However, we have had requests in the past, and I think it is an important demo of how how you can prevent a kernel extension from matching to one of your interfaces. And the solution is to actually create a codeless kernel extension that matches to your device or your interface and uses the I/O service class as the driver. The I/O service class itself, all it does in its start method is return true, And that is enough to prevent other drivers, especially class drivers like the HIT driver, to match to your device. Because your vendor-specific code let's extension will have a higher probe score. So I have a sample of that kernel extension.

And you can see, hopefully, that there is no code. We just have some resources and the product. They're just an info.plist. I'm not going to go through the whole thing of the info.pill list. It's very standard, but the first couple of key values right here are telling I/O Kit that the CFBundle that we're going to use for this driver is actually I/O Kit itself, and that is where the I/O Service class lives. We also tell it that the class to use for our driver, i.e., where the start method is, is I/O service.

In this case, our provider class is an interface. And then we have our matching criteria. In this case, again, I'm going to use the wildcard matching criteria to match to all the interfaces in our device. You can see, however, not however, but you can see that they are not numbers but strings. Just like in the-- in user space, we use a CFString to pass the value to the matching dictionary, for I/O Kit personalities, we have to specify a string. Of course, if this was an integer, it would not work. So we go ahead, build it. and I think it built. Yup, it did. Then, I need to load this kernel extension. So I cd to that directory. It has to have the right permissions. So... I go ahead and change them to root wheel.

and we see that at least the top level is indeed root wheel, so I think that it takes. And now... I'm going to load that kernel extension, sudo kexload -v. And it tells us that everything is OK. And it actually loaded one personality in the kernel. Now, we have 30 seconds before the I/O Kit unloads that kernel extension. I want to make sure that we are still logging. We're going to clear-- actually, I'm going to unplug. clear and replug. The reason that I'm unplugging is to give the new driver a chance to compete when I plug in the device. If I didn't unplug, the device or the interface has already had a kernel extension attached to it and it wouldn't load the new driver.

At level five, we do print some information on drivers that are loaded for a particular device or interface. And we can see right here, that we found a driver with a score of 88,000. and a wildcard value of two. As you recall, we had a wildcard for the configuration value and the interface number. Usually, a vendor-specific device at this level has a score of 90,000. We reduced it by 2,000 because there were two wildcards. If we go now to the IO registry, we have that refresh automatically set here and open our... interfaces, we see that the I/O USB HID driver is not loaded for it anymore and now it's I/O service.

Now if we go back to our previous project, where we got the exclusive access error, Run it again, and this time we don't have an error. Recall that we're just doing an interface open and close, and so there wouldn't be anything else. So this showed you how we could use a vendor specific codeless kegs to prevent a class driver from grabbing your interfaces or your devices. And it comes handy in lots of cases. especially when you want to deal with the data by yourself and you don't want anybody else to be using it.

Now let's actually go and do something with this data. Let me clean up here a little bit so I can see where I need to go. This is just another version of the same program. And if we go to work with the interfaces, we see that I actually have the interface open routine here. I don't have that closed because now we're going to actually try to do something asynchronously. We're going to try to go ahead and read from the interrupt pipes. We have, as you recall, two interrupt in pipes, one for each interface, so we're going to set it up so that we can do that. my iterator is already looking through all the interfaces that match our matching dictionary. So it's going to call the work for interfaces routine twice, once for each dictionary.

I go back to our demo monkey stuff here. The first thing we need to do, because we need to use the interface number later on, is get our interface number, and these are just calls through the I/O USB library into the interface objects. We then need to get the number of endpoints. As we saw earlier, this device, you know, only has one interop end point, but if you had more, then you need to know how many so that we can iterate through all of them and get the end point that we are interested in. So we'll show here that we're gonna iterate through all the pipes.

through all the endpoints and get a reference to each pipe. Those of you that are in the USB list might have seen some questions a couple of weeks ago about pipe references and interfaces only having one pipe but we reporting or we showing on pipe reference of zero, et cetera, et cetera. The idea to take away here is that there is always, by the USB spec, a default control endpoint, and that has a pipe reference of zero. And that belongs to the actual USB device. Interfaces have endpoints, and we create pipes for each of those endpoints. And those are the things that have pipe references higher than zero. I'm starting at zero here just so that we can see how the control endpoint looks.

But you would probably just start at one, because that's all you need. If you were gonna do a control request from you via the IOUsb interface, it's gonna ask you for a pipe reference, and you're gonna specify pipe reference zero. Then while we're inside this loop, We go and we get the pipe properties for each pipe.

And this just gives you all the information for the pipe, the interval, if it's in or out, the max packet size, what type of pipe it is. So once we get that information, we want to actually look for our interrupt in pipe and do a read to it. So what I do here, is verified that it is indeed an interop in-pipe and then called read from pipe async. We go up there and there's nothing there right now, but first thing we do is I want to do asynchronous reads and writes, or actually in this case just reads. So we have to create an asynchronous event source and add it to our run loop.

And since I want to keep reading from this pipe, what I need to do is, in my callback, reissue the read. So I need some global data, in this case, to access when my callback is issued. And so I'll create a handy little structure here that has the pertinent data. Of course, in real life, you probably have more to it. And it has the interface interface that we need to access the I/O USB library, the number, the pipe reference, etc, etc, and where to put the data. And then I create a global one for each of the interfaces in our device, because I know that we have two interfaces and we're gonna be called twice.

Then we go back to our read from PyPageSync. And we actually now need to-- create the per interface data using that structure that we just created. As you recall, we are passing the interface number so that's how we can key off of it. And we issue the read right here.

Now this read_py_pay_sync is an I/O USB interface call. And you can look at all the parameters. Essentially, we have the callback function. and a void star that can be our RefCon data and that is just going to be passed into our callback when the read completes. We're almost there. Now we have to go and-- go to our asynchronous callback function, that is right here, and look at the data and then issue the read again.

The callback function will have three parameters. The first one is the void start that was passed in the original call. Then we have an IO return that specifies the error, the result of the callback. And there's another void start that is arg0 here, and that is the number of bytes that were transferred by the USB read. In some cases, if you're reading, for example, from a bulk pipe or writing to a bulk pipe, you're going to request 64K of data and the device might only send you 23K. That is perfectly valid. It's a valid transaction on the bus. There were no errors. It is ended by a short packet, but you want that data. So what we would have in that situation is that the number of bytes transferred here would be 23k, even though you had requested 64k. So that is passed to you by the USB stack. Once we are in the callback, I have a printf that I'm forgetting here. This is this one.

so that we see it when we're running it. It just tells you what it returns. Then you would do something with this data in real life, actually. And again... Once we do that, we would reissue the read. Now, something to note here is that I'm not chaining reads yet. I'm issuing a read, and once that completes, I'm reissuing that read. So we -- if the device was sending a lot of data, because of the inherent work loop latencies, like I mentioned earlier, we might not be polling that device fast enough, and your performance will suffer. But in this case, it's an I sync or an interrupt device. I forget what the polling interval is, but it doesn't matter. Okay, so let's build and then run it. Oh yeah, I remembered this last night.

Let's just remove that because there is no exit. So we'll build again. And now let's run it and see what-- whoa, OK. I'll stop it. I cannot make this bigger, unfortunately, but I'll go through it quickly. It says that, it just all my printfs from my program. Interface number 0 has one endpoint. The endpoint 0 actually is a control endpoint. It has the any direction, because control can be host to device or device to host.

a max packet of eight, and the polling interval doesn't matter here. Now for interface number zero, endpoint one is an interrupt endpoint with a max packet size of four and a 10 millisecond interrupt interval. We then go and discover the other interrupt pin in the second endpoint. And then we have issuing a read pipe for interface 0. And then, like I said, we're iterating through all the interface. So later on we show an asynchronous read for interface number 1. And my callback function gets called. And the result is return success.

It tells us that it was interface 0 that we got. And transfer number one, in this case, we're not chaining reads, so that does not change. And bytes transfer was zero, and these are the first four bytes of it. As it turns out, I'm actually not sending any commands to this device to start sending data from interface number one. So that's all it's going to do. And I stopped it, but It demonstrates that we were issuing the reads from both pipes and we were getting the data and reissuing the read, and this would have continued on and on and on. So that's a simple example. Now we're going to... add another device, and actually we're gonna use essentially the same code. But this other device is this digital camera that has a USB video class interface. And I know that it is a bulk pipe camera as opposed to an isochronous camera. So let me plug it in.

You know what I'm going to do, actually? I'm going to plug it in through my CATSI. And so at the end of the program, I can actually show you the data coming in through the -- and being decoded by the USB bus analyzer. So the first thing I want to do is make sure that we saw that device. So I go to USB Prober here.

Do a refresh, and here it is. It's a USB PC camera, and as you can see, it has a bulk input. On highlight-- oh, sorry. Is it there? Now it is. Sorry about that. It works in the other-- in Xcode. OK, so I get now version 4 of my demo program.

First thing we have to do, I didn't show you before, is that we need to change the vendor and product IDs to the new camera. As an aside, with Xcode 2.0 and native targets, now you have to specify the info.plist. You actually-- it opens in just a regular Xcode window editor, or you can open it with the plist editor. I've discovered that you can input a hex number instead of just a decimal number by prefacing it with 0x, and it'll take it and it'll do the right thing. So that's kind of handy. So again, we now have our vendor and product ID. And we want to not only look for any interrupt in pipes-- and I didn't show you, but this device actually has an interrupt in pipe as well-- but we also want to look for the bulk in pipe.

We come and here where we're iterating through all our endpoints, we're going to go ahead and add the Bulk-- that's not where I wanted it. Let's put it-- So we're going to look for a pipe that is either an interrupt pipe or a bolt pipe and the direction being USB in. Then, with this device, what we want to do is actually read-- issue more than one read more than one read at a time. So let me actually go and modify my data structure here. to make it -- uh-oh. Okay. I jumped ahead of myself.

back to work with interfaces, I believe. You know what, let me go to the one that I have everything there because we're running out of time and it'll be a lot easier. You see I have the checkpoint versions just in case. Let's go back to work with interfaces. As you can see, we are looking for the interrupt and the bolt pipe, like I was saying earlier. Now when we go to read pipe async, what we want to do is create data for each of the reads that we're going to issue, and that is my number of concurrent transfers that I'm going to issue at a time. I modified my global to be a two-dimensional array with the interface number and the transfer number, and I remember the transfer number right there. The other... calls are the same. The calls to read_pipe_async, they need a pointer to the buffer where we're going to put the data, what the max packet size is, etc. So that doesn't change. And because we are in this for loop, we're actually going to issue the call twice.

If we go back to our callback function, as you can see, it looks the same as before. And if I was actually typing, you could see that I wasn't going to change it at all. At the beginning, once we get the callback, we only have to reissue that one read, because we're going to get another callback later on. And that's how we keep the bus prime with our reads. And of course, up here, I-- modify this to be the two-dimensional array. Let's build this. OK? Let's bring the Run window. At this point-- well, let's run it first. Didn't do much.

It's just for kicks. I want to compile it again. Run it. Ah, OK, cool. So you can actually see right here that-- and I'm not going to go through all the things, but we discovered the interrupt endpoint. We discovered the bulk endpoint, and we created two reads at a time. And we can see that we're getting callbacks with 64 bytes and transfer 0 and then transfer 1. If we could go to the Windows machine just for a second, and we have This is the Cat C software. We're going to set it up to record. And I'm going to run the program again. Let's make sure that-- OK, it's running. I'll stop. Let's see, let's do stop but preserve uploaded data. And here you can see-- view, zoom out-- no, zoom in.

Let's do one more. Yes, yes, I know I am running out of time. You can see that 64 bytes at a time, but we were so close. And I can just continue. And you see, actually, in this case, we haven't talked about it, but you see the data toggle actually toggling between 0 and 1 in each transfer. And at some point, we're probably going to see-- here that the device gave us as much data as it could, and now it's starting to knack the read.

from us and that means that it's actually sending video data and it didn't have, it's probably the blanking interval. Anyways, I hope that that gives you a sense, if we can go to the slides again, of how to start with debugging USB drivers. We have kitchens every so often, and we noticed that there was some interest in -- in knowing how to start with USB, and that's why we did this demo like this.

Of course, there's the URL where our -- all the information about WWDC is, the related sessions. Tomorrow, we have a kernel extension lab next door where FireWire USB I/O Kit people are going to be. There is also a lab for adding HIT support to your applications, and our hit guru, Rob Yepez, is going to be there, so I encourage you to take advantage of that.

Craig Keithley over here is going to come for the Q&A. He is our evangelist, I/O technology evangelist. Of course, if you don't know about the USB list at http://list.apple.com, join it. We always read it. Sometimes, depending on our workload, we don't answer as fast as you might want, but we are there, so please use it.