Hardware • 1:05:07
Learn how to develop both user space and kernel model drivers for Mac OS X. This session discusses the architecture of the USB Family in Mac OS X and how to access USB devices from applications. Creation of USB kernel extensions and how driver matching works for USB kernel extensions are also covered. A demonstration of debugging USB drivers in Mac OS X is featured.
Speakers: Fernando Urbina, Rhoads Hollowell
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon and welcome to the USB In-Deepth session. We're going to go into USB both for user client and kernel level development. I'm Craig Keithley. I'm the USB and FireWire technology manager. Actually, I'm one of two. Greg Mullins, also in my group, is another USB and FireWire technology manager. So with this session, we'll go into what's involved with writing
[Transcript missing]
Today we're going to give you an overview of the IOUSB Family class in Mac OS X, talk a little bit about the objects that we have and how we use them in the kernel. We're going to go into some detail into writing kernel and USB drivers and how to decide whether to be in the kernel or in user space.
A very critical part of writing drivers is how to match your device to your driver. So we're also going to go into quite a bit of detail into that. And at the end of the session, we'll show some demos on how to debug USB drivers in the kernel and in user space.
As you probably know, you will need to use Project Builder, which is Apple's integrated development environment, to write both kernel and user space drivers. If you are writing user space drivers, you will be able to use the debugging features of Project Builder to debug your driver. Unfortunately, if you are a kernel space driver, you will have to get down and dirty with GDB and terminal and debug your drivers that way.
Later on, I'm going to talk about whether you should write a user space or a kernel space driver. This is the first decision that you have to make when you decide that you're writing a USB driver for Mac OS X. So it's very important to know what you're going to do there.
If you are in the kernel, you will be able to use IOCAD's object-oriented features. So for example, if you are writing a driver that is going to be subclass from our mass storage driver, you will only have to write the methods that you're overriding to implement your device-specific features.
So it's a very convenient way of doing it. Rhoaads will talk later on about user space drivers in some detail. And as was mentioned in the previous session on Firewire, we use the device interface model in order to communicate between user space and our internal I/O USB family objects.
The USB family is in the kernel. There's four or five objects that we provide. They are delivered as a kernel extension that goes in the extensions folder. We use this facility to update USB without having to give you a whole new kernel. User space drivers can obviously access the internal objects through the device user or the device interface client that I mentioned earlier.
This slide gives you just a brief description of the internal objects for the IOUSB family. Those are the ones in purple. And in a later slide I will talk in some detail about them. I just wanted to give you an overview of where we fit within the user space and the kernel space.
So let's talk a little more about kernel mode USB drivers. How do you decide whether you're going to be in the kernel or in user space? If at all possible, you should not be in the kernel. However, in some cases you don't have a chance. If you are writing a vendor-specific driver for a keyboard, You need to be in the kernel because at boot time if you're going into single user mode in the machine, you need to have a keyboard.
There's no user land processes running at that time, so you have to be there. The other condition is if your clients live in the kernel. For example, mass storage or networking clients, the file system or the TCP/IP stack, they are in the kernel. You have to be in the kernel, and so you don't have a choice.
Again, if you haven't gotten the message, we really recommend that you write your drivers in the user space. They are a lot easier to debug. If you're in the kernel, it's really easy to panic the machine, and users don't like that. So try to write user space drivers.
Just like all other I/O Kit drivers, USB drivers are written in the subset of C++. The base class of all I/O Kit drivers is I/O service. When you write the driver in order to load it against your device, you will need to provide a matching dictionary so that we can load it.
Later on, we're going to show you on the demo machines the different fields that go into a dictionary and how we decide on how to match it against your device. The driver itself that Project Builder makes is a KEXT package that goes in the extensions folder. And as we mentioned earlier in the previous presentation, the IO USB Family or your driver can include sub-drivers in the one package.
This slide just gives you an overview of the different USB family objects that are in the kernel, the I/O USB device, I/O USB interface, and I/O USB pipe. I'm going to talk in more detail about each after this slide. We also have two other The objects which are the I/O USB device user client and I/O USB interface user clients. These are the user client objects that are used to communicate between user space and our internal objects.
When you plug in a USB device into a bus, our family will create a I/O USB device object for that device. The provider of that device will be the I/O USB controller class. The I/O USB device object is an abstraction of the physical I/O USB device. This class provides methods for accessing the device descriptor fields of the USB device.
We also provide methods for getting the configuration descriptor of the device and also for setting the configuration. configuration for that device. Once you set the configuration for the device, that family will go ahead and create I/O USB interface objects for all of the interfaces that are in that particular configuration. The I/O USB device class will be the provider for all these I/O USB interface objects.
Again, there is going to be one I/O USB interface object for each interface in the particular configuration description of the device. The interface objects are an abstraction of a USB interface. The methods that we provide for the USB interface are those that allow you to access the fields of the interface descriptor and also allow you to set alternate interfaces if they are available for your interface.
When we create the I/O USB interface object, we will also instantiate I/O USB pipe objects for each of the pipes in the interface. There will be one for the default control endpoint and we'll also create however many objects. The I/O USB pipe objects are those that are needed to match against all the other endpoints that are specified in the interface descriptor.
The methods that we provide for the I/O USB pipe objects are those that are needed to actually communicate with the device. So we have the read pipe, we have the write pipe, we also provide methods to get status on the pipe, to clear the... a possible stall in the pipe, etc.
The other two objects that we have, as I mentioned before, are the IOUsbDeviceUserClient and the IOUsbInterfaceUserClient. And these provide the necessary glue for a user task, user driver, to talk to the internal objects. You could tell whether you actually have a user task attached to your device driver or your interface driver by using the IOU Register Explorer, going down the tree to find your driver node. And you can then see if there is actually one of these user clients attached to it.
The convention that we use in our family for the Apple Supply drivers is the following. If the driver starts with the I/O letters, we intend that you could subclass that driver and add functionality just by defining new methods or overriding some of the base class methods. If the driver starts with the word Apple, that is ours and you're not allowed to subclass it. However, if you have a vendor-specific device, you could just get our code adapted to what your device does and use it that way and create your own driver.
This is a list of the USB drivers that we provide in the kernel. These are all the Apple ones. It's the standard mouse, keyboard, hub driver. We provide, just like in Mac OS 9, a composite class driver that will just look at that configuration, find the first configuration, and do a set configuration on that device to create the interfaces for that device.
The two subclassable kernel drivers that we provide are the HIT driver and the mass storage class driver. Again, the nice thing about living in the kernel and being able to use these drivers is that if your device just is a little different than one of the devices that we support, you can just use C++ and override those methods that you need to change, and you have a lot simpler driver than starting and writing everything from scratch.
Those of you who attended the USB overview session yesterday probably will recognize the following two slides. We are including them here because we think it's really important to understand how the driver stack of the USB drivers is built up and in the next slide how it all fits together. So again, the IO PCI family will discover the OHCI USB controller and it will load a driver for that device. That is our IO USB controller driver.
During startup, this controller driver will instantiate a I/O USB device for the root hub of that controller. We use IOPKID methods to match a driver against that device and we have the... The Hub driver sits there waiting for devices to get plugged in. For example, if you suddenly plugged in a pair of speakers, the Apple USB Hub driver will notice, it will enumerate those speakers, and it will create an I/O USB device and attach it to the I/O USB controller.
In this case, for this example, speakers are usually a composite class device. So the composite class driver will go ahead and load against this I/O USB device. It will do a set configuration on it, which as I mentioned earlier, will load the Create the I/O USB interfaces for that device. In this case, I'm only showing one of those interfaces.
Again, we use the I/O Kit matching code to ask for a driver to be matched against this interface, and we have the Apple USB audio device driver loaded against it. So this is how this works. And any device that you plug in will do the same thing and will try to find drivers for it and instantiate them for your device as long as they're in the kernel.
This driver is, oh sorry, this slide is here to show a distinction between being a driver and The distinction that we want to make is who your provider is and what member of which family you are. For example, the Apple USB audio device is a member of the IEO audio device. This is the base class from which they derive.
They get their behavior, their API, from the... IOU Audio Device Family. However, when they actually want to send data to the pair of speakers, they use the IOU USB Family as the transport mechanism to get that data from memory down to the speakers. So it's sort of a dual relationship. You are a member of the family, but you are a client of the transport family, which in this case is the IOU USB Family. But it could be another mechanism, another family. If there were FireWire speakers, you could have FireWire.
In this example, the Apple USB audio device is a leaf node driver, so it is not intended to be subclassed. However, if we had a head driver attached to an interface, like for the buttons of a speaker, as you've seen in other slides, it has a little puzzle piece, which means that you could subclass that driver and add the functionality that you need.
In Mac OS 9, we had a DDK that provided some example drivers. Some of them, or most of them, we used internally at some point, but they weren't the live sources that we were using to release the USB stack on Mac OS 9. That has changed with Mac OS X. We are open sourced. We live in the Darwin repository.
So our live IOSB family sources are there. The module name is the IOSB family. No surprise there. If you haven't gone and become a member of the open source project so that you can check out the sources, you should go ahead and do it. Download it. Look at it. Look at the source. Look at the drivers. Borrow whatever you need. Tell us where we have the bugs.
It would be nice if you said, you know, line 23 of such and such a file. But it is there for you to look at. And we went through a lot of effort to get this out into the open source community. So you are more than welcome to go ahead and look at it.
Okay. I/O USB family driver matching. If we cannot drive your driver, if we cannot match your driver to your device, then game over. Why have the driver? So it's very critical that we explain it so that you can get it right. And the first item that you have to decide is are you going to be, other than being in kernel versus user space, but once you have decided that you are a kernel driver, you need to decide whether you're a device driver or whether you're a composite driver. You will specify in your driver personality, and we'll show you a demo where that goes in a little bit, whether you need to specify the I/O provider class.
Other I/O Kit drivers use the I/O probe score inside the personality to give a hint to I/O Kit of how important this driver is, what the ranking is. In the I/O USB family, we do not use and you do not need to provide the I/O probe score. This is because USB and the USB specs have a very definite set of criteria that spell out how multiple drivers are ranked and which one has the highest rank and which one has the lowest rank, so that if we find n drivers, we can rank them and we can tell I/O Kit, "Okay, try this."
[Transcript missing]
and I/O Kit will load that driver first and give it a first shot at controlling that device. So again, the USB Common Class Specification is the document that details exactly what fields you need to have in your personality to match against drivers.
Just by chance, here I have a copy of the table that is used by the common class spec to specify device as opposed to interface driver rankings. All the fields in the boxes are fields of the USB device descriptor. At the top level, with the highest possible rank that a driver can have, is a driver that specifies the product, the vendor, and the, it's called BCD device, but it's the release number of the device. One step lower, we have the vendor and the product that will just match against all drivers of that vendor and product ID without caring about that release number.
Down in the orange boxes we have what we call the class drivers. These are classes that are not vendor specific and that is what the 0xff means. You can match against the class, the subclass, and the protocol in the next to last item or just device subclass. So as we're going to show you, you're going to specify these and this is what the USB Family uses to generate a probe score for I/O Kit to decide which driver is the highest ranked driver.
As was mentioned in the overview yesterday, once I/O Kit has a list of drivers that match to a device, it will give each driver a shot at denying whether they want to control that device or not by calling the probe method of that driver. The default implementation for probe method is to just return success, so you don't even need to implement it, but it is just here to give you a shot at denying control of the device.
Later on, you'll still get another shot at denying control of that device. Once the ordered list of drivers with the higher one at the top and the lowest ranking one at the bottom is created, I/O Kit will call the start method for the highest ranked driver first. This tells you that your driver is really starting. You can do all your stuff that you need to do to get your driver in working condition.
If you return false from this method, then I/O Kit will go, okay, well, he didn't want it. Let's try the next one in the list. So you get this second shot at it, but now I'm going to show you, or here, Rhoads is going to help me out. If we could have demo one machine on the slides. I'm going to show you how to specify the matching dictionary.
So we have the project builder. This is a sample vendor specific driver that we wrote. And this pane that we see up there is the bundle settings tab of the project builder target. In there we have an I/O Kit personality dictionary entry. This is where the matching criteria, among other things, is going to live.
The first item that I'm going to talk about here is the CFBundle identifier. This string needs to match the CFBundle identifier that is declared in the upper level. The best way to do it is just to copy and paste. Because if it doesn't match, your driver won't get loaded.
The next field is the I/O class field. This is the class of the driver that That you're going to use. So here we have the header file where the C++, my software vendor, my software company class is defined. That is what you need to put in that entry. Next, you need to tell us whether you are a device driver or an interface driver. And so you specify I/O USB device or I/O USB interface. In this case, we have a vendor-specific device driver, so we have I/O USB device.
Now, we don't have here, but Rhoaads is going to type in. We decided to make this a vendor-specific device driver, and we're going to type in the common class spec fields that are required for that device. And it is the ID product, it's a number, and the ID vendor, and it's a number. Those numbers correspond to the, obviously, the product and device fields of the device descriptor.
Finally, we have this I/O Kit debug dictionary entry. This is used internally within I/O Kit to put debugging information out to the console, to the system log. It helps some in debugging why your driver doesn't load from an I/O Kit perspective. However, right now it just says that matching failed. It is also used internally within I/O Kit for other features. It is actually a bit field of different features, but if you put 65, 535 in it, it will match to all the features and it will put out a lot of data. Amen.
We do realize that right now it is very frustrating to determine why your seemingly good driver is not loading against your device when you plug it in. We are very aware of this and it's not going to stay like that for long. We will use the I/O Kit debug field to actually put some meaningful information out to the system log to give you an idea of what was going on when we tried to match your driver. The final two... Two entries in the bundle settings tab here are the OS bundle libraries. This is a dependency entry. It tells I/O Kit that this driver is dependent on the com.apple I/O Kit.iousbfamily version 1.8.
If you were depending in the future against an API that was introduced in version 1.8.7, you would put here 1.8.7 and I/O Kit wouldn't even load you if the iOSB Family version 1.8.7 was not around. So that way you don't have to do runtime checks whether a particular API is available like you used to do in Mac OS 9. The final property is the OS Bundle required property.
There is a driver matching that goes on very early on during the boot process. This property tells IOCAD which drivers need to participate in this early driver matching. For example, our mouse and our keyboard drivers have this property because you, as I mentioned earlier, need the keyboard to go into single user mode.
The reason I mention it here is because if you are writing a vendor-specific keyboard or mouse driver, And you want to override our USB class driver, you need to also put that property there. If not, what's going to happen is the early on driver matching during boot time will load our class driver. And then when I/O Kit, well, I/O Kit then won't even scan your keyboard or your mouse for a driver because it already has one loaded. So you need to specify that if that is what you are doing. If we could go to the slides now again.
This slide shows you which I/O Kit methods are called during the two-step driver loading process. The first step is when I/O Kit is looking for drivers for a device. As I mentioned earlier, the probe method is here, which gives you a chance to deny your matching. You get the init, the attach, the probe method, and then you get detached again.
Once I/O Kit decided that you are the one, that you are the driver who is going to control that device, You get an attach method again and then you'll get the start method. The thing to take out of this slide is that your attach method will get called more than once, so you have to be prepared to deal with it.
When your device gets disconnected, we get the following termination sequence. You first get a message method with the KIO services terminated message. This is your clue that your provider is going to go away. You need to cease and desist all I/O to your device. You need to stop everything, return from it, then you'll get a terminate message followed by the other, sorry, a terminate method call followed by all the other ones that I list here in the slide. The key there is the first notice that you get that your device has been unplugged is this services terminated message. If you had your provider open your device or your interface, you need to close it at that time.
Now if we could go back to the demo machines, we have a vendor specific driver that is a very dummy driver. All it does is it spits out IO logs, which by the way an IO log will print a message in the system console every time that the message is called. Fernando Urbinan, Rhoaads Hollowell And this is just to prove you that it's not smoke and mirrors and we are actually calling all those methods in the order that we said.
So here we have a security dongle and Rhoaads here is going to load the driver so that it's available for when we plug in the device. Fernando Urbinan, Rhoaads Hollowell He'll then open or do a tail on the console log so that it's available for when we plug in the device. Fernando Urbinan, Rhoaads Hollowell So that we see any messages that get displayed when we go ahead and plug the device. Fernando Urbinan, Rhoaads Hollowell And it crashes, no.
There we go. We get the init method followed by the attach, the probe, the detach, and again the attach method and finally the start and we successfully start and there we go. Now when we disconnect the device, we unplug it, boom, we get all the other methods and your driver after a minute or so will actually get unloaded from memory. So now I'll give keyspan to Rhoaads and he'll tell you more about user space drivers. Alright, we need to go back to the slides please.
[Transcript missing]
There is also a user client object and a device interface or an interface object used to communicate with an IOU USB interface object in the kernel. Unfortunately, there is some confusion because the generic name of these objects in user space are device interface objects. The term "USB" is used in the USB world because of the use of device descriptors and interface descriptors. So what we really have with USB is IOU USB device-device interfaces and IOU USB interface-device interfaces. Which of course is difficult to say, much less fit on a PowerPoint slide. So we have shortened that to be device interface and interface object. and an interface interface.
So, what do you need to do if you're going to write your USB driver code in user space? Well, you need to use two frameworks in particular. The core foundation framework, which gets you access to things like CFPlugins and CF run loops. And the I/O Kit framework, which gets you access to the I/O Kit functions as well as the USB functions, which are part of the I/O Kit framework.
This mechanism uses, as I've said before, the CF plug-in model and the two plug-ins provided by USB are the IOUsb device interface plug-in and the IOUsb interface interface plug-in. We do not provide an interface plug-in for the IOUsb pipe object. This is encapsulated within the plug-in for the interface mostly, although there is one pipe in the device, which is the default control pipe.
But the other pipes are all part of the interface. In the kernel, those pipe objects are instantiated when the interface is opened. And then you have access to methods within the interface interface. And then you have access to methods within the interface interface plug-in to determine the characteristics of a pipe and to then send and receive data over any of the given pipes.
The I/O Kit API is in, as I've said, in the I/O Kit framework and it's in, much of it is in the I/O Kit lib.h file. The plugin API that we use is also in the I/O Kit framework and it is in IOCF plugin.h. And then finally the USB interface API is in the I/O Kit framework in the USB subfolder and it's in Iousblib.h. Again, the two interfaces we provide for USB drivers are the Iousb device interface and the Iousb interface interface.
So, if you're running in user space, you need to communicate with a kernel object, either an I/O USB device object or an I/O USB interface object. So how do you do that? Well, the first step is to find the correct kernel object. These procedures, by the way, are outlined in a document available from the website called Accessing Hardware From applications or something along those lines.
So what are the steps involved? Well, first you have to, from user space, you have to obtain a Mach port that you use to communicate with the kernel. Then you have to create a matching dictionary that's very similar to the matching dictionary we showed in the demo of the personality.
And the same rules apply to this matching dictionary. You must fill in fields from either the device descriptor or the interface descriptor based on whether your driver is looking to be a class type driver or a vendor specific type driver. So you add these fields by adding qualifications to this matching dictionary, and then you ask I/O Kit to give you a kernel iterator of kernel objects that match your particular dictionary.
So how do we obtain the master port? Well, the API is defined in Mac.h and it's just a call to IOMasterPort. When you're done, you should deallocate that port to tell I/O Kit you're done using it. So how do I create the matching dictionary? Well, it depends on whether you're looking for an IOUsb device or an IOUsb interface. In the device case, you ask for an IOService matching dictionary with the device class name. If you're looking for an interface, you ask for the dictionary with the interface class name.
Then you add qualifications to the matching dictionary. Again, these are the qualifications. We showed a table earlier. They are from the section 3.10 of the USB Common Class specification. And in the current shipping version of the I/O USB Family, the one in 10.03 and in 10.02 and 10.01 and 10.0, we're very strict with this matching dictionary. What that means is that if you want to match, say, the second line, which was ID vendor and ID product, you must include both of those fields in your matching dictionary.
And you may not include any additional fields in your matching dictionary, such as the device class or the device subclass, or else your matching dictionary will fail. So, and you also need to be aware when creating these matching dictionaries of the different rules in this section of the common class specification for matching against I/O USB device objects and I/O USB interface objects.
Once you've added the qualifications to your matching dictionary, the next step is to get a list of services within the kernel. These are the kernel objects that are subclasses of I/O service, like every kernel object, I/O Kit kernel object is. And I/O Kit will return to you an iterator of all of these services. You then can access these services one at a time. to determine if it is in fact the object that you wish to control.
So to use this I/O service T that I/O gives you back through this iterator, you then can call I/O create plugin interface for service. This is the call that instantiates the pair of objects, the device interface or interface interface on the user side with the device user client or the interface user client on the kernel side. This call creates those two objects, sets up that communication channel and allows you to then communicate with the object that you want to communicate with.
So if you were creating a user client, if you were looking for a user client for the USB device object, you would use the first call here with KIOUSB device user client type ID. And if you were interested in the interface instead, you would do KIOUSB interface user client type ID. These big long constant names, by the way, are defined in IOUSBLib.h.
Once you have this IOCF plugin interface, this is actually a very small interface that does not quite yet represent the device or interface you're looking for. You have to then call a function called query interface to say I'm looking for this particular type of interface, whether it's the device interface or the interface interface.
Now let's say I have an I/O USB device interface. What can I do with it? Well, this would be pieces of a standard initialization sequence. You would open the device. You would then get a pointer to the configuration descriptors for this device. You might then find the configuration value out of one of those configuration descriptors and call set configuration.
And then once you've called set configuration, you would be interested mostly in looking at the different interfaces that have now appeared on this device. So you would create an interface iterator. And this would in turn return to you I/O service T tokens for the various interfaces attached to this device and you could create interface interfaces for each of them to find the one you're looking for. When you're done, of course, you should close that USB device. and release the interface.
So, now that I have an I/O USB interface interface, what might I do with it? Well, you would call USB interface open to obtain exclusive access to this interface. You could then find out how many endpoints this interface has in it. You might choose an alternate interface if you know that there is one.
You might get the properties of each of the pipes in this interface. You could then call read pipe and write pipe to read and write data across this pipe to the endpoint you're interested in. And when you're done, you would close the interface, close the I/O USB interface, and then release the interface interface.
How do you do asynchronous I/O? From user space, you don't need to be careful about doing synchronous I/O versus asynchronous I/O. Because the user space threads are never running on the work loop, it's okay to do synchronous I/O all the time. If, however, you want to do asynchronous I/O, then you need to take your interface, whether it's the device interface or the interface interface, and create an async event source and add that event source to your CF run loop.
And documentation for CF run loop is in CF run loop dot h. And you would then call CF run loop run and your event source and possibly more than one event source would then get processed. By that CF run loop, calling back the callback routines for your asynchronous calls.
Again, the USB specific header file in the I/O Kit framework is listed above. As I said, it's in the I/O Kit framework and it provides the API for both the device interface and the interface interface.
[Transcript missing]
Okay, now we're going to switch to the demo machines and we're going to do a little bit of debugging, a little bit of a demo on how to debug. We're going to show you first how easy it is to debug a user mode driver or application and then we're going to actually attempt to do a two machine debugging for kernel mode driver using both machines.
Do you have a breakpoint? No, no breakpoints. Okay, so here is my user space driver. It's actually just written as a Unix tool. So of course it has the main routine. And what I'm going to do here is I'm going to go ahead and just put a breakpoint right there. Is it built? Go ahead and build it. I'm going to go ahead and rebuild the driver just to make sure that it's built.
And then I'm going to hit the debug can to show you that here I am. I'm at this line of code. I'm creating my master port. I have all my arguments up here. This is the project builder interface to GDB for those of you who haven't seen it. So I create my master port.
I create my, let me scroll this up a little bit. I create the matching dictionary. I'm saying I'm looking for an IOSB device. I'm going to go ahead and plug the device in. Well, Let's see, I need to-- I can actually see-- where is it? I can actually show you the console, the GDB console and the printout statements that we stick in here, you know, appear on that console. So I create the matching dictionary.
I add the property for the vendor name and the product name, which are really the vendor ID and product ID. Someone has filed a bug with us that those are terrible constant names and I agree. I create a notification for this particular type of device and at this point I think I actually will have been notified about it because I've already plugged it in.
So what I'm going to do is I'm going to go into my notification routine and... I'm going to set a break point at the call to create the plug-in interface. And I'm going to continue. OK. So now I'm at the Create Plugin Interface for Service. And I'm going to go ahead and bring up the IO Registry Explorer.
So this is the device I just plugged in. It's called USB Token. That's the name string that's inside the device. What we can see here is that there is no user client attached to this device yet. This user client init object is used to tell I/O Kit which object to create when someone asks for a user client. So now if I go back to my debug statement here and create the plugin interface for service. Now if I go back to I/O... Well, it should have created it. Hold on. Oh, I need to do the query interface. That's got to be what it is.
Well, looks like I/O registry is not... Okay, there's the device user client. I don't know why this version of IO registry is not updating, but there's the IO device user client, which is the kernel side object of the device interface pair, so that I now have a user mode application communicating with this IO USB device object.
So then what I will do is I will call get the vendor, get the product, and get the release number. We can see that the vendor, if I look in my variables here, the vendor is 1209, which is what I expected, and the product is 768, which is also what I expected.
I do a little test to make sure it's what I expected, and it is. And so I open this device using the USB drive. I'm going to do a quick device open function. Then I'm going to configure this device. And so I'm going to first determine how many configurations there are, and I can see that there's one configuration.
And I'm going to then get the configuration descriptor pointer for this configuration. And here is the configuration description pointer. The configuration value that I'm going to end up calling set configuration with is a 1. And I call set configuration. And now, IO Registry Explorer does update. And when I go and I look, In fact, now I have an interface associated with this device, attached to this device.
That interface appeared in the kernel because I made a set configuration call. But I didn't make it directly in the kernel. I made it from user space and it went through the device interface and created the configuration. So that's pretty much the end of this user debugging demo, but you can see that it's pretty simple to debug this using Project Builder and GDB because you're in user space and you're not trying to step through code in the kernel. So that's the user space driver demo. Again, it's much easier, so we highly recommend that that's where you put your driver.
However, in the case where you really need to be doing a kernel mode driver, for example, a mouse driver, keyboard driver, mass storage driver, and so forth, We're going to show you the techniques that you need to use to do two-machine debugging with GDB. This stuff is defined in an I-O-Kit document, debugging an I-O-Kit driver or something like that, that goes through these steps, but we're just going to show you that, in fact, it can be done.
So the first step that you need to do when trying to set up a two-machine debugging environment is you have to know the hardware Ethernet address of the other machine. You look at the ARP table, for those of you who are Unix people, and you say, and the first thing you've got to do is you have to delete the ARP entry for the other machine. So I'm going to do that. Then you have to create a static entry for that IP address.
For that Ethernet, that IP address to that hardware Ethernet address. All of this again is documented in the debugging kernel driver document. Now that I have that connection, it's a permanent connection in my ARP table, which you can see. And so now if I have the driver over here.
Can we have demo two? I can talk to it. So can we split the screen and put demo two on one of them? Okay, he's going to load a driver and we have a debug statement in the start method of this driver. He's going to plug it in. And he's frozen because he's got this debug statement.
So, So I'm going to load GDB and then I'm going to say target remote KDP. This is in that document again. And then I'm going to attach to this other machine. I am now connected to that other machine. I can do a back trace and I can see that I'm in a debugger call, but I need the symbols for my driver.
I will use a command called show all Kmods, which shows me all of the kernel modules that are loaded on this machine. So that would be-- That one. And then I'm going to say, okay, oops. Here's the address of my driver. And now I need to create symbols for this driver, which I built on this machine. So I do KMODSIMS. And because it is dependent on the I/O USB Family. And I need to know the address of the I/O USB Family, so that's in here as well. So you can see that debugging is somewhat more painful when in the kernel.
Okay, I gotta get the address again. Okay. Now I have a symbol file for this, and so I add this symbol file to my... to my GDB session. And now if I do a back trace, I can actually see all my line numbers. And it didn't put it in.
Well, OK. So we are out of time. Basically, I can then, I'm attached to this thing, I can go ahead and continue. So this machine is now up and running again. You can see by that. And he can unplug the device. And oh, it kernel panicked. Well, we were actually expecting that.