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: wwdc2008-447
$eventId
ID of event: wwdc2008
$eventContentId
ID of session without event part: 447
$eventShortId
Shortened ID of event: wwdc08
$year
Year of session: 2008
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2008] [Session 447] Maximizing ...

WWDC08 • Session 447

Maximizing Platform Compatibility of I/O Kit Drivers

Essentials • 36:59

When writing I/O Kit device drivers, you need to support both PowerPC and Intel-based Macs, multiple operating system versions, and 32-bit and 64-bit address spaces, all from a single code base. Learn how to structure your driver and configure build settings to make this possible. Also learn how to use the Leopard user client APIs to enable communication between 64-bit applications and your driver.

Speaker: Jay Towslee

Unlisted on Apple Developer site

Downloads from Apple

SD Video (449.9 MB)

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Okay, we're here for Maximizing Platform Compatibility of I/O Kit Drivers. That's a mouthful. My name's Jay Towslee. I'll be your host for this morning. And what we're really talking about is how do you get your drivers to work everywhere you want them to work. Kind of simple. Yeah, right.

We're also here to talk about K64 because that's probably the biggest change that you're going to undergo next few weeks. It's really that simple. I should say this does have nothing to do with the iPhone. So, if there are iPhone sessions that you wanted to go see, now would be a good time to go see them.

So what you're going to learn. We're going to talk about extending your I/O Kit code base lifespan. How many people have got code that's been around since 10 mumble, mumble, mumble? A few of you. Yeah. So ideally, it's not something you want to throw on the floor. In order to give you that kind of Education or hints, whatever.

We're going to go through a decision matrix which will help you understand where to kind of make the cutoffs, cutovers, which APIs to move to, that sort of deal. And we're going to spend some time looking at Xcode because the new Xcode actually has done some nice things for KEXT development.

We're going to start with a very short history lesson. I promise, very short. In the beginning, after the Earth cooled and the dinosaurs came, there was 10 and 10.3.9. That was pretty straightforward. We had one architecture, PowerPC, 32-bit, user space, 32-bit, kernel space. Everything was relatively straightforward. Binary compatibility was good. In a KEXT developer's life, yeah, we made some changes, but that was relatively straightforward.

Then we gave ourselves Tiger. Now, there were tiny bits of 64-bit support, and that was sort of the beginning of you starting to have to think about those kind of problems. But in general, we're still pretty good on the binary compatibility. Life is pretty simple. And then we did this to you. We gave you Intel. Sorry about that. Well, not really.

The good news was that we kept the PowerPC support around. How many people have implemented Rosetta support in their texts? The same guys that have been writing the KEXT since 10-- OK, interesting problem. So we'll talk a little bit about getting Rosetic support into your KEXTs, as well as how you're going to deal with PowerPC-only issues, Intel-only issues, and so forth. Then we gave you Leopard. Now the good news with Leopard was the matrix doesn't change a whole lot, so that kind of gave you time to get your stuff in order.

The bad news was that that's kind of when we really modernized everything. We got 64-bit user space, and really, you needed to have your text be 64-bit internally, or at least understand how to deal with it when somebody contacted you that way. And then finally, we are where we are today, with the Snow Leopard preview.

Now, if you haven't read yet, the Snow Leopard preview doesn't boot on PowerPC. So at least you don't have to test that right now until we do something later. But you do have K64. How many people have K64-capable machines at home? Okay, it's the same group of people. I think you should exchange business cards afterwards.

Yeah, they have their own support group. The other change that's kind of significant is that in 64-bit kernel space, you can have a 32-bit user process contacting your 64-bit driver. So you need to understand what's going to bite you in the butt there. So that's kind of the history lesson in how we got to where we are today.

A little review for what we're really talking about. Kernel land is different. Our driver executes in the kernel. You need to protect the rest of the--you need--your code needs to not crash. Ugh, let's-- Why is that important? Because if you crash in the kernel, the whole system goes in the dumper.

That would be bad. Don't do that. Applications execute in user space. How do we cross the divide? Well, we're doing that in I/O Kit land. And common services are vended applications by system services, which are the things we're all taking advantage of, BSD land-type things, and the more user-accessible devices that we're all plugging into our machines all the time, USB, FireWire, et cetera.

If your device doesn't require custom access beyond what the families give, though, that's good news. I/O Kit families pretty much handle the low-hanging fruit for you. There are even things called codeless KEXTs, which are literally that simple. If you don't need custom access, you're done. Anybody here doesn't need custom access? Because if you don't, you can--no, okay. Otherwise, I/O Kit provides two bridges, I/Octal type controls or mechanisms. That's through the I/O registry. That's pretty straightforward. We're going to look at that. For a lot of people, that's all you need.

That's all you're going to need in your KEXT. If you need--depends on your point of view, I suppose, higher level, lower level access, then you're going to be in I/O user clients. It's pretty straightforward. Basic process, define a set of entry points in a common header. We'll look at that.

Your app--now, we don't usually recommend putting it into an app. We're typically going to put you into a driver or a framework or, excuse me, a library or a framework that then apps can call. But some people are building it directly into their app. That's kind of a stub function for each entry point. And then the I/O user client subclass in your KEXT is what actually is going to implement those tasks.

So the I/O registry, for those of you that weren't in Dean's talk earlier, very quick review, it's what we're using through the I/O Kit framework to locate and communicate with drivers. We can locate drivers in the registry by properties, so that's how you differentiate between a driver for card A and card B.

Apps can read and optionally, and I do say optionally, the default behavior is I/O Registry is read only. We're going to look at an example of how to make I/O Registry read and write. Again, for that situation where your need to cross the kernel boundary to actually move data back and forth is pretty minimal. And then if you want to extend or need to extend because of the functionality of your device, you've got the I/O user client and plugin, again, plugin, library, etc., etc., which will let your app have a direct connection into the kernel.

Now, with I/O User Client, big gotcha. You are now--we are holding you, at some level, responsible to make sure that you're not going to let bad things happen in the kernel. So remember, you're exposing a library or a stub or something, which may be an argument for putting it into the application itself. You're exposing that to other applications to do with what they will. So you want to make sure your API is as small as humanly possible.

You want to validate the access. Make sure that you're talking to who you think you're talking to. And related to that, if you check out Apple Smart Battery in Darwin, you'll see an example of using the I/O user client, Client has privilege subclass, which you may find useful. But again, be careful out there. Because as we all know, that's the last thing we need, is to have the Mac become the same home for problems that some other operating systems have become.

Before we go further, again, I wanted to talk about kind of the easy, the minimal thing that you can do if you need custom access from user space. And it's a single API. It's got great compatibility. In fact, we've got code that will run easily from 10.3.9 all the way forward to 10.6 with basically an ifdef to say, oh, yeah, this time I built it for a 10.3.9 text. And it's really pretty straightforward. and that's the I/O registry.

We're going to look at I/O Registry in detail, but now I want to go through the operating systems that we have historically needed to support. So Panther is PowerPC only. No I/O DMA command, which is sort of the modern way of dealing with memory. 32 applications only. For I/O user client access, you had get target and method for index, and that was the only way you could do it.

Now, realistically, and I think if people were in Bertrand's State of the Union, he's suggesting that probably 10.3.9 is not the way to go at this stage. If you have existing code, though, there's no particular reason to throw it away, particularly if it works. If it doesn't work, maybe you should consider throwing it away. The SDK, however, is not supplied with Snow Leopard, so you'll need to get that from some previous build of the OS that you have laying around. It does install on Snow Leopard. It does work on Snow Leopard.

With Tiger, in particular, once we got to Universal, you've got Intel and PowerPC, Rosetta, which I think is probably the most important thing that you can do kind of minimally to make your life easier. 32- and 64-bit application support, barely. LibSystem gave us 64-bit support. The I/O user client functionality, we're still a get-targeted method for index.

Structures for parameter passing is something that we've started to recommend at this point, and that's primarily because it simplifies the design. And I think you'll see in the example where using a structure to shuffle data around in and out, as opposed to a whole bunch of scalers, will make your life easier. And finally, you had the problem if you were dealing with an older PowerPC code base, you still had I-O memory cursor laying around, and you really wanted to start moving the I-O DMA command at this point.

Leopard, more decisions. To me, the decision point comes down to kind of your code is sitting at 10.4 and earlier, and then if you're going 10.5 and later, and we'll show you some tricks on how to ship sort of one kex to do that, but that's kind of the great dividing line.

Leopard is sort of the modern world, and 10.4 and earlier is sort of the, I don't want to use, really use the word prehistoric, but kind of the earlier way to do things. We got a new way of dispatching methods called the external method. Everything from Tiger still applies and becomes more important. I/O DMA command is the way to deal with memory in KEXTs. I/O Memory Descriptor goes to 64-bit support for user land.

And if you, the good news is, is if you have gone through and modernized for Leopard, then for the most part, within a couple of hours, and clicking the 64-bit checkbox, and the hours are like testing and resolving warnings, you're probably going to be Snow Leopard K64 text aware. Life will be good. So, you know, if you do the work for Leopard now, you won't have to do the work for Snow Leopard in the future.

If, however, you're going to start at Snow Leopard, and that's kind of your jump-off point, a couple things to know about. Obviously, you've heard earlier in the week the 64-bit kernel. There are two Intel KEXT architectures. You have to have a 32-bit and a 64-bit KEXT. The 32-bit KEXT will not load.

In fact, I think when I talked to some of the folks after the K64 lab, that was one of the sample solutions to how to tell you're on a 64-bit kernel. If the 32-bit KEXT only doesn't load, you're on a 64-bit kernel. Support for 64-bit addressing everywhere, in and out, both sides.

IO memory descriptor is what you want to use for that, and there's a couple of pieces that were available in Leopard and later. Availability.h, which is going to let you ifdef your code. We'll look at a sample of how that works when we look at the sample code. And the C preprocessor, you're going to learn to know and love.

Again, this is the issue where you're taking an existing code base, you want to keep a single existing code base, and bring it forward. If what you're trying to do, it depends on your development methodology, but what we're going to concentrate on is the case where you'd really like to keep things and just sort of migrate as opposed to, here's a clean sheet, or here's the copy, and here's the build for that. The obligatory I chart in every presentation at WWDC. These are the big changes between K64 and previous, with the I/O memory descriptor. The good news is all this stuff is A, in the documentation that's on your DVD.

Release notes, thank you. It's early. Maybe I did go to the party and I just didn't remember it. So I'll get the eye chart off, but most of these are a drop-in, one-for-one replacement, and it's actually pretty easy to do this update. The other issue-- how many people were at the K64 talk earlier in the week? A lot of you.

Good. So this slide should look familiar. This is one of those deals where you didn't realize you had the problem until you had the problem. And it's all about alignment. Now, we have to remember that different compilers for different architectures have different rules about how they're going to lay things out.

And if we compare the 32-bit and 64-bit versions of this same structure, you'll see we've got some padding issues. So the A tag comes out beautifully, but then we go to-- because we've defined the B pointer as just a void star on a 64-bit machine, that's now an 8-byte-wide structure-- or excuse me, entity. That causes us a problem. We have to deal with that inside the kecks, because this pad that we weren't expecting showed up.

We're okay on most of the rest of this, but we also have this ending pad that could bite us. So on your text, if you haven't thought through the alignment issues in the data you're passing in and out, you're going to have up to four code paths, 32 to 64, both directions, to deal with pulling these structures apart and updating them.

So what we're recommending is now is an opportunity, even though you may lose a few bytes or bits, depending on how much shifting around you have to do, now is the opportunity to sort of rethink the structures you're passing through. It does mean that you're going to have to learn a little bit more, maybe than you had to before, about the way the compiler lays things out, but it's kind of a small price to pay.

And the reality is that's something that we're going to be stuck with for the foreseeable future because compilers change all the time. We're going to move on to some demo now. We're going to look at Xcode, the modern Xcode 3.2. If you can switch me to demo A, please.

So this is a piece of sample code that will be available almost immediately. We hope that it's going to be up a little later today for you to take home. And it's a pretty simple project. It's a PCI driver that matches on every display card in your system. We'll start by looking at the targets themselves, because this is where we were talking about making it more compatible.

And I apologize that I can't get this text larger. One of the things to note from Xcode in the new version is they got a lot more context sensitive about where they offered you certain options, and it got easier. You no longer have to guess where you're going to add something.

You basically can choose any field and add a build setting based on architecture, SDK, etc. So that's kind of handy. What we do have to define for this particular project is 32- and 64-bit universal. You'll note this is the Leopard version of this kex, Leopard and Snow Leopard version. We've defined SDKs for the PowerPC and Intel.

Those are the 32-bit versions of the OS, and we've defined the Intel 64-bit as 10.6. The part that really will get you is in two places. One, picking the correct compiler for the architecture. So as you may remember, if you're running Leopard, the default compiler on that was 4.0 GCC, and the default compiler on 10.6 is 4.2.

The part that is actually handy for us that we're going to be able to use for the code is the deployment targets. Those basically will set up symbols in the compile time, which will allow you to make decisions as to what part of the code you're going to compile and load for that particular driver.

If we look at the target for the 10.4 version of the code, we don't ask for Google, we unclick. Yipe. Let's try this trick again. You're not going to see a whole lot of changes. We are 32-bit only. We've gone to the 10.4 SDK everywhere. You will note we've reduced the complexity of The architecture argument, and everything's GCC 4.0. What we did add is a dependency on the other text and a copy where what we're doing is we're going to add a copy of the other text, is copying that file into...is it here? I apologize.

The KEXT that we built for 10.4 gets copied into the plugin directory of the 10.5 and 10.6 KEXT. That will cause it to load only on 10.4 systems. It's kind of a hack, but it works. This is more targeted at the situation where, from a support perspective, you absolutely want to ship exactly one text.

So, one of the things we talked about earlier was the I/O registry. And I wanted to take a quick peek at what we actually stuffed into the I/O registry for purposes of changing settings, managing different values in your driver that then your driver can react to. So we've added a little dictionary. You could certainly construct this on the fly in your driver.

It's just a lot more code to demo, and so I chose to actually add this dictionary to the plist itself. And we're going to be looking at the display parameters section, and we're actually going to be changing the value of the brightness. It's pretty straightforward. Again, this is something you could do all internally in the code.

So what we need to look at is in the code itself, we're going to define that in a shared file, and we're going to just put some arbitrary values because it's a lot easier than remembering to write brightness, and that way I change this in one place, normal programming technique. And those are the, these keys are what we're going to be using in our code.

All right, so let's say we wanna look at the actual client. This is your user space code. This would, again, normally be a library that you're gonna implement. But in this case, we've rolled the whole thing into an application. So the first thing you're going to do is you're going to actually match. If you stayed with Dean's talk, you understand what you're doing here. Basically, this command is going to match on every device that's relevant for your key. Then we're going to iterate through those, each device in kind, and we're going to do a couple of things.

If we found it, which we're going to today, we're going to run a test on the properties, which is where we talked about doing the I.O. registry settings, and we're going to do a test on the user-client piece, which is the more advanced section. So if we look at the properties, we're going to jump over here.

And properties that we're doing with I/O registry are all going to be sent back and forth via dictionaries. That's a little more complex, as you can see, to do the setup, because I have to do some core foundation magic. But then the call's relatively simple once I've put it in. I'm making one call to this I/O registry entry set CF properties. This is something that, again, by default, you are going to have to override. By default, this is going to return, nope, sorry, nothing was set, you're not allowed.

So let's go over onto the kernel side and look at what's actually happening. Now what I chose to do on this was not Not actually pull this into the user client code. That's purely a stylistic thing. You can put this in the user client. You can put this in your main driver. It really doesn't matter. This is the only if-def that you'll have to deal with for 10.4 to later with this technique, and it's pretty straightforward. You're matching on the correct KEXT name.

So the set properties itself is a simple override. Again, you do have some core foundation-like. We did not implement core foundation in the kernel, but we cherry-picked the pieces that we needed, where we now need to deserialize effectively that dictionary. Now, there is one immediate advantage to this.

You'll notice that I'm not doing anything worried about Rosetta because the serialization of getting it in and out of the dictionary took care of any Indian-ness issues that I had. So once I've set the value after pulling it out of the dictionary, or pulled the value out of the dictionary, then I'm going to do the update registry. Now, the update registry call is a little trickier, partially because we do not have protection, if you will, for the I/O registry collections.

It's not protected against multiple writers. So the first thing you're going to do is grab a copy of the properties that are relevant to this particular dictionary. Then you're going to copy that collection and throw it into another dictionary. If you don't, your code will panic, I promise.

That makes it really easy to debug. When it panics and it says, "I think you did that wrong," and it literally will call out, "I think you've just touched the I/O registry in an unsafe way," you'll know this is likely your problem. You also know things about the actual structure of that dictionary.

So in this section of code, I'm actually iterating down effectively through the dictionary until I get to the point where I've got the value that I wanted to change. At that point, I can make one call to set property. That's serialized and a safe way to get the data back in.

So it's really pretty straightforward if all you need is to do simple kind of command and control sorts of things, enable features, etc., etc., on your driver. At that point, your other code in your driver is going to be going around going, "Hey, I just noticed a change in the registry. Now I can go do things based on that change." All right.

So again, that was pretty straightforward, I think. I hope you agree. And then setting is, or getting is free. Getting doesn't require any code on your part. You're basically querying the registry. You're querying it with the I/O registry entry create CF properties, which is basically taking a snapshot of your existing match. You're going to look up the value you're looking for. So in this case, I'm matching on the name that I actually matched for the device.

And then I'm going to do on the user space side some core foundation work, in particular because this is a string, to pull that string out of the dictionary and use it however I want to use it in my program. Again, so setting and getting properties out of the registry, simple, very compatible. There's a little bit of overhead more than direct, but that's about the only downside. All right. So you may remember. back here.

Whoops. I may remember where I am. Oh, yeah. Sorry, lost. No, I don't think it's a pop-up to the left. You're right, it's a pop-up to the left. Thank you, Nick. All of this to say, first we're testing the properties, and then we're testing the user client. So the user client, again, like I said, is more work, and it's more useful. A couple of quick things.

You have to connect to the driver if you're going to access the user client at all. And this is kind of the first place where we're going to start seeing if-defs. If you're running 10.4 or earlier, you're going to be using structure I, structure O. If you're running 10.5 and later, you should be using I/O call struct method.

And we're actually not doing any setup for method one. We're just going to go in and use it. So we do need to look at one small thing. Pardon me. That is there. And at the top of the shared header, you're going to actually declare an index of the methods.

And that's actually how we're going to cross the kernel boundary. We're using Mach messaging to do this. All of it is hidden from you. And believe me, you don't want to use MIG if you don't have to. For those of us in the front row that have to use MIG, it makes us very happy. All right.

So we go back to the user client, and it has made the call, and on the way in, It has to actually distribute the call. Now in 10.4 land, you're going to distribute via get target and method for index. And it's a little bit more complex to set up.

It isn't--well, it's a relative term. I just should say I prefer the other method. The one limitation that you have with getTargeted method for index that would prevent you from using it going forward is it doesn't support 64-bit scalers. If you're passing structures around and you're used to using this, you can stick with this for 64-bit KEXT development.

The method that you're using if you're running, say, Leopard and later, and what I would recommend if you're starting new development is to basically take the selector, that same index denumeration we saw earlier, and we're going to call down here and based on a switch, call the different methods. Again, it's more a conventional programming approach, which is going to make it easier if you have to share your code or have other people work on it for you.

So we're going to call method one. And method one, again, is very straightforward. It's a very simple example. Right up, we're coming in, we're checking the input size, we're going to do some calculations. And then we have this magic, cross-Indian. This is how we're going to implement Rosetta support. Again, Rosetta support is pretty darn straightforward if you know what to look for.

So, when we initialize this driver, we're actually going to, on its call in from the outside world, we're actually going to check and see Whether or not this object KIO user client cross-Indian key exists. If it exists, that means you're on an Intel machine. If it doesn't exist, you're on a PowerPC machine, and the rest of this is irrelevant.

So assuming that you're checking because you actually want to support this, once you've found out that you're on an Intel machine, you're actually going to set the key KIO user client cross-Indian compatible key. So that now you've told whoever's talking to you, yeah, I can deal with Rosetta issues. And you're going to set this cross-Indian flag to true.

[Transcript missing]

Pretty straightforward. All right. Now we're going to look at one more example that's a little bit more complex.

And what we're doing is actually allocating some memory and then passing in some parameters, in particular, handled to the memory. What's important here is looking at this fabulous number. 1, 2, 3, 4, 5, 6, 7, 8 is pretty much guaranteed to look really funny if you get the end in this wrong.

So it's actually a really handy number to have around if you're testing your code. Pretty much the same way of calling in. So we're gonna go back to the user client. We're going to look at the second method. Again, we're getting here the same way. We're either going through the selector or the get target method for index.

And in here, again, we've got some Rosetta testing. We're checking and swapping the bytes if we need to check. And now we get into the whole I/O memory descriptor issue. And again, I think you'll notice as we go through this that you're looking at samples that are pretty close to, "Yeah, I had A before.

Now I use B." This is also one of the few places that you're going to be occasionally if-deffing with PowerPC code as well as the version. So, for example, here we have the I/O memory descriptor with address. Note that we have a VM_address_T. That's a real strong hint that, yeah, this is really 32-bit only. 64-bit code's going to have mock_vm_address_T.

And the good news is, is this will actually fail in a nice way and give you a backtrace in the logs. Also, I believe I heard somebody in Dean's session earlier asking whether there was a way to do single machine debugging. Here's an example of an I/O log that we're going to see when we run this a little bit later.

And we did ifdef, and so this is the case where if you're on Tiger, or excuse me, if you're on Leopard or Later, we basically have taken with address range and dropped it in as a wholesale replacement, not any changes on your part beyond not doing the wrong thing with the size of the variables you're passing around.

We have a similar kind of deal down here with memory mapping. You're going to swap out map for create mapping in task. Again, really easy to do. And like I said, that's in the release notes and that's on the I chart slide, but this stuff is pretty darn straightforward.

And the last example would be dealing with get virtual address and using get address instead. So that's all in the sample code. I should show you how it works just to prove that we didn't come up here to blow sunshine in various locations. Boy, that probably doesn't translate very well. So here's our keks that we have loaded.

And here is the console, so we'll see the debug output. So right now, if we look, we have all the different various configurations. So let's run the PowerPC version. of the client. Certainly, you could have a--you're going to expect to see universal clients, but for ease of testing, I've broken these out to each individual architecture. And next time, I won't type it.

So it's run, and those answers are correct. You'll have to trust me on that. When you look at your code, you see that. And here it is with those I/O logs on the other side. I should point out real quickly that despite something we may have all wished for at some level, the-- The PowerPC 64 code doesn't work on Snow Leopard any better than it did on Leopard. This is an Intel machine. Okay, that's kind of what I wanted to show you for the demo. And let's go back over and see what we've got.

So kind of to summarize, K64 development helpers, again, this was cribbed kind of directly from the K64 session. So if you saw that, I apologize. You want to be moving to the GCC 4.2 as a system compiler. Again, if you're doing the compatibility thing, you're going to play with the options in Xcode to do the right thing for the keks that you want to ship. You're going to replace calls to deprecated APIs. That's really critical for K64 because they're not just deprecated, they're removed in K64.

There's a couple of really key warnings you're going to want to enable and resolve. WALL and shorten 64 to 32, which will keep yourself from shooting yourself in the foot, as I've been known to do on occasion. You're going to add support for 32-bit and 64-bit user space. And if you're not using Xcode, there's a couple of flags you're going to want to set.

Now, if you weren't in the K64 session, the actual session number from Tuesday was 338, so when you're looking it up on iTunes, that's what you're going to want to look for. And right after this, we're going to talk about text management changes that we've made in Snow Leopard, so you may want to stay tuned for that.

In summary, you want to add 64-bit support for your driver. If you do it in your Leopard driver, your K64 work for Snow Leopard is pretty much done. So the good news is you can actually add it as a feature to something you can ship today, as opposed to doing the work and going, yeah, well, whenever Apple gets around to shipping that, we'll be all set.

You want to consider your portability options, and really that's about what choices you're going to make, where you're going to make your cutoffs, how are you from a supportability perspective going to deal with this stuff. Are you going to ship the single KEXT, or are you going to ship multiple KEXTs and deal with the support calls there? Having been on the other end of tech support phone calls, it's kind of six and one half dozen the other.

You want to clean your code in 32-bit mode before moving forward. Another really great advantage of the K64 stuff is that if you are on a K64 machine and you're having a problem, you can certainly quickly regress and see if you introduced the problem only on K64 or not by running, rebooting the machine in 32-bit mode. If you already have, if you've already done all the Leopard modernization, you're pretty much done. Congratulations, you can go home now.

If not, you do have a little bit of time to do that. You do have some work to do. It's not onerous. I think the longest Apple driver to come over, modulo that we haven't done the audio stuff yet, was three, four days. This is not, this has not been a huge effort for us. I hope it's not a huge effort for you.

For more information, your contacts are Craig Keithley. There's some terrific documentation about getting through this stuff in the 64-bit transition guide in terms of how you want to deal with 64-bit things. Also, you want to check the release notes because they're going to show you the one-for-one swaps on all the I/O memory descriptor stuff. So, we have time for some questions.