Tuesday, March 22, 2016

Loading Firmware

<< Prev: Basic Proof of Concept       Next: #include Woes >>

Loading Firmware

After recognizing the hardware, the next thing I wanted to do was identify and load a firmware file that would work for the hardware in question. The code with the device IDs that I originally found (in here) included a configuration struct that included most of the firmware file name -- everything except the version number -- and the range of acceptable version numbers (see, for instance, all the config objects in the bottom half of this file).

It seems like the Linux API lets you request a firmware file and get a callback with either the file data or an error (request_firmware_nowait). So the iwlwifi driver just attempts to load all possible firmware files from highest version to lowest version until it finds a match. To resolve that kind of request, Linux talks to a user-space process that looks for the files in question in a directory such as /lib/firmware, and the user is expected to put the firmware files there.

I didn't see any similar infrastructure in OS X -- something to sit on top of data or configuration files in a directory and serve them up to kernel extensions upon request. Long-term, this might be a challenge. It would be obnoxious to build a user-space daemon to sit on a directory like /lib/firmware and the methods for the kernel to communicate with it. Plus there would need to be a way to ensure that loaded before any driver that needed to request firmware from it. But other drivers (even existing Bluetooth drivers) have the same problem, and without some common option like that every driver would have to implement its own solution, which seems unfortunate.

Still, there's a short-term solution. Kexts can include arbitrary files in their Resources directory, and I found the OSKextRequestResource call load file data out of Resources. So I could load the firmware file from there. I tried bundling a firmware file into my kext and tagging it as a build resource and loading it with a hardcoded file name, and that all worked! (See my original loadFirmwareSync method, and the callback just above it.)

Actual firmware naming logic

The next step was pulling in the logic that actually held a specific firmware file name for each piece of hardware. It turned out there were five possible firmware files. I briefly considered just building my own mapping of device IDs to file names, but all the information was already in the Linux driver code... I just had to find a way to take advantage of it.

The problems were thus:
  • The code was split across three files. One defined all the 7000-series device configuration possibilities. One defined the 8000-series device configuration possibilities. And the third listed every possible device by ID, along with which configuration it mapped to. Often many specific devices mapped to a particular configuration, and more than one configuration mapped to a given firmware file.
  • All three files were .c files instead of .h files, meaning it wasn't a completely straightforward #include to pull that logic in
  • The third file had a bunch of additional logic. It was all related to how the driver registered to be loaded when those particular devices were present. The more I looked the more it seemed like the same kind of plumbing I had built into my Kext -- the Linux equivalent of the IOKit registration, startup, and shutdown processes. I didn't need that code.
So I took the easy route of converting the first two files to headers (iwl-7000.h and iwl-8000.h), and pulling the array of devices out of the third into a new header file (device-list.h). You may notice a few changes. In the first two, I just commented out the calls that link modules to firmware files, since that's all Linux-specific plumbing. If you compare the third to the device list in the original, you'll see that I simplified the struct that holds the device information. I didn't need the full Linux pci_device_id struct, just the device ID, subdevice ID, and configuration struct.

Then, I duplicated the logic of the Linux driver that checked available versions of the firmware file for each device. To do that, I sort of adopted some of the structs used by the Linux code, such as iwl_drv, iwl_cfg, and iwl_trans. These I pulled out of wherever they were defined into my own code. So I ended up with a revised loadFirmwareSync method and a few header files that I either migrated directly (like iwl-config.h) or created to hold a grab bag of #defines and structs from files I wasn't ready to convert.

The Joys of Kext Development

It was about this time I found out what I was messing with. I had allocated some object or other that I freed when I finished using it. Then (whoops) I freed it again in the overall Free function of my driver.

So I tested my new hardware-matching, firmware-loading kext: "sudo kextload AppleIntelWiFIMVM.kext". All good. Yay!

Then I cleaned it up so I'd be able to try a new version later. "sudo kextunload AppleIntelWiFiMVM.kext".

Then my screen turned off.

Then I heard the little Apple chime.

Then my laptop informed me that it had restarted because of a problem.

So much for the "good old days" of Segmentation Fault; Core Dump. Instead I had an inscrutable panic report, with some elaborate complaint about 0xDEADBEEF. I guess this is what passes for humor among kernel developers? Though, I guess I should thank them for bothering to make my pointer point to something obvious when I freed it the first time, so they could tell when I freed it a second time.

Anyway, it appears they weren't kidding around when they said in case of error, the kernel would shut itself down rather than risk filesystem corruption and associated problems. Still, I had like thirty windows open!

Well, I've learned my lesson and now test my builds on another machine. On the upside, I can test on a machine with the actual Intel wireless hardware, and I was able to turn SIP back on for my laptop. But now it's a two-machine operation. I guess that day was coming anyway, since there's only so far to go without using the real hardware.

<< Prev: Basic Proof of Concept       Next: #include Woes >>

No comments:

Post a Comment