Prologue
Now keep in mind, I’m writing this article now in 2023. It has been 2 years since this whole fiasco, even though the post says “posted in 2021.” That is a lie.
Storytime with Kevin
TWO years ago, I decided to turn my Dell laptop into a Hackintosh. The specific model was the Dell Inspiron 5567 (you can still see the serial number taped to the screen in the image below). However, there were some challenges – especially because I wanted to boot the MacOS image off an optical bay caddy. That’s right! I stuck a 1TB drive into the optical bay for more storage.
The problem with this (of course) is that the optical bay SATA connection was designed for a 3GBps (Gen2) connection, not a 6GBps (Gen3) connection. Aside from Windows, both Linux and MacOS had trouble detecting this drive. This was because I had stuck a Gen3 capable drive into a Gen2 capable link, and the OS simply assumed a Gen3 connection. Except for Windows – it seemed the Intel AHCI drivers were doing something fancy.
On Linux, this was easy to fix: just set the libata.force
kernel parameter documented here and you’re good to go. On MacOS, you’re out of luck. Unless you’re crazy enough to reverse-engineer and patch the kernel!
Which I am.
But XNU is open source! Right?
So no. The drivers are not. Peeking at the IORegistryExplorer, AppleACHI is the driver responsible for SATA connections. This corresponded to the AppleAHCIPort.kext
KEXT file. A Kernel EXTtension is analogous to the loadable kernel modules in Linux. However, we’re so deep in the kernel that this KEXT is not even an IOKit-derived driver. Instead, it probably is the backend of the IOKit ATA API, documented here.
I did thoroughly search the IOATAFamily
.kextand
IOAHCIFamily.kext` drivers but got nowhere. I still have no clue what they do. My methodology was to search for the text “6 gigabit” in the string tables because some driver somewhere was reporting that.
There were certainly a lot of hopes and prayers behind this.
Yippee! From this, we can see there are 2 cross-references of this symbol: one is in InitializePort
and one is in ScanForDevices
.
Two paths diverged in the woods
The segment from InitializePort
looked like this:
We can see that the code is simply reading an already-initialized value from this + 0x140
and setting pcVar13
based off of that. Which is ultimately used in IOAHCIPort::SetAHCIProperty
. A dead end. Well, what is “this”. It turns out “this” is part of CtlnaAHCIPort
(AppleAHCIPort but a renamed version for use in BigSur because of this shenanigans).
Let’s rename this field and continue. In ScanForDevices
, we see the following:
We see that the value in uVar9
is compared and a speed is assigned accordingly. Now, uVar9
is a monstrosity. How good are you with pointer arithmetic? Let’s break it down:
1 |
|
To read this, employ the spiral rule!.
- First interpret
this->unknown + 0x138
aslong*
and dereference - Next, offset that
long
by0x28
- Interpret as
uint*
and then dereference again!
You got it! Let’s rename this field and continue. For reference, this is what our struct AHCIPort looks like now. This is a screenshot of the structure editor of Ghidra, a very useful feature!
And full definition in C++:
1 |
|
Now uVar9
becomes this->some_uint_ptr->gen
. Much nicer!
It’s a bird… or a plane… or PxSSTS?
Buckle up and grab a fresh copy of the Intel Serial ATA AHCI Specification v1.3.1. If we just trace through the usages of this gen
field in Ghidra, the very first method that comes up is AHCIPort::ReadPxSSTS
. Now, in Section 3.3.10 of the AHCI specification, we see:
1 |
|
and if you recall in AHCIPort::ScanForDevices
, we read this register and mask it with 0xf0
to obtain the generation. Indeed, this is the Current Interface Speed (SPD) field of this register! Although this isn’t what we want – we want to set the current interface speed, not read it. Reading on, we see that the next register, PxSCTL, contains a field Speed Allowed (SPD) which does just that!
This is quite simple now! The struct some_uint_ptr_struct
is actually the HBA registers and our some_uint_ptr
field is actually a pointer to the HBA base. Let’s rename our structs:
1 |
|
and now we just search for all uses of PxSCTL
and replace them so that they are OR’d with 0x2
(for Gen2 speeds). It turns out that Apple doesn’t do any sort of link training at all – and they don’t need to – since they assume all their PCBs will be designed to meet the reasonable specs of the drive.
In the end, I only patched AHCIPort::EnablePortOperation
and AHCIPort::HandleComReset
as those were the remaining functions dealt with SATA hot plugging, which I don’t care about. I hope you enjoyed this journey as much as I had! See you in the next article.