Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
custom:
- "https://github.com/hrx114514x/PC711Probe/blob/main/SUPPORT.md"
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 1.2.0 — 2026-08-07

- Added early, PC711-only MSI-X allocation for Darwin 20–22 without taking ownership away from Apple `IONVMeFamily`.
- Removed the direct dependency on the unexported legacy `IOPCIDevice::configureInterrupts` symbol.
- Added a legacy `CreateDeviceInterrupt` route for Darwin 20–22.
- Hardware-validated macOS 13.4.1 Recovery; macOS 11.6 remains unresolved.
- Changed the v1.2.0-and-later project license to PolyForm Noncommercial 1.0.0.
- Added bilingual voluntary cryptocurrency support information.

## 1.0.0 — 2026-08-07

- Enabled automatic operation without `-pc711pcompat`.
Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ Before opening a pull request / 提交 PR 前:
git submodule update --init --recursive
./Scripts/verify.sh
```

Unless explicitly agreed otherwise in writing, contributions accepted into the v1.2.0-and-later development line are provided under the project's [PolyForm Noncommercial License 1.0.0](LICENSE).

除非另有明确书面约定,合入 v1.2.0 及后续开发分支的贡献均按本项目的 [PolyForm Noncommercial License 1.0.0](LICENSE) 提供。
19 changes: 16 additions & 3 deletions Driver/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,24 @@
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>1.2.0</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<string>1.2.0</string>
<key>IOKitPersonalities</key>
<dict>
<key>PC711EarlyMSIX</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.stationk9.driver.PC711Probe</string>
<key>IOClass</key>
<string>PC711EarlyMSIX</string>
<key>IOPCIPrimaryMatch</key>
<string>0x174A1C5C</string>
<key>IOProbeScore</key>
<integer>100000</integer>
<key>IOProviderClass</key>
<string>IOPCIDevice</string>
</dict>
<key>PC711Probe</key>
<dict>
<key>CFBundleIdentifier</key>
Expand All @@ -35,7 +48,7 @@
</dict>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright 2026 StationK9. BSD-3-Clause.</string>
<string>Copyright 2026 PC711Probe contributors. PolyForm Noncommercial 1.0.0.</string>
<key>OSBundleCompatibleVersion</key>
<string>0.1.2</string>
<key>OSBundleLibraries</key>
Expand Down
137 changes: 108 additions & 29 deletions Driver/PC711Probe.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0

#include <IOKit/IOService.h>
#include <IOKit/IOFilterInterruptEventSource.h>
Expand All @@ -17,9 +17,84 @@ constexpr uint16_t kPC711Device {0x174A};
constexpr uint32_t kNvmeClassRevisionMask {0xFFFFFF00U};
constexpr uint32_t kNvmeClassRevisionValue {0x01080200U};
constexpr uint32_t kInterruptTypeMSIX {0x00020000U};
constexpr size_t kConfigureInterruptsVtableSlot {0x960 / sizeof(uintptr_t)};
constexpr size_t kControllerFlagsOffset {0x191};
constexpr uint8_t kLegacyMSIXPathFlag {0x10};

using ConfigureInterrupts = IOReturn (*)(IOPCIDevice *device,
uint32_t interruptType, uint32_t numRequired, uint32_t numRequested,
IOOptionBits options);

bool isPC711Device(IOPCIDevice *pci) {
if (!pci)
return false;

const auto vendor = pci->configRead16(kIOPCIConfigVendorID);
const auto device = pci->configRead16(kIOPCIConfigDeviceID);
const auto classRevision = pci->configRead32(kIOPCIConfigRevisionID);
return vendor == kPC711Vendor && device == kPC711Device &&
(classRevision & kNvmeClassRevisionMask) == kNvmeClassRevisionValue;
}

IOReturn configurePC711MSIX(IOPCIDevice *pci) {
if (!pci)
return kIOReturnBadArgument;

auto vtable = *reinterpret_cast<uintptr_t **>(pci);
if (!vtable)
return kIOReturnUnsupported;

auto configure = reinterpret_cast<ConfigureInterrupts>(
vtable[kConfigureInterruptsVtableSlot]);
return configure ? configure(pci, kInterruptTypeMSIX, 1, 1, 0) :
kIOReturnUnsupported;
}

} // namespace

// Darwin 20-22 may have resolved a different PCI interrupt allocation before
// IONVMeFamily creates its event source. Request MSI-X while the PC711 PCI nub
// is still being probed, then decline attachment so Apple's driver takes over.
class PC711EarlyMSIX : public IOService {
OSDeclareDefaultStructors(PC711EarlyMSIX)

public:
IOService *probe(IOService *provider, SInt32 *score) override;
};

OSDefineMetaClassAndStructors(PC711EarlyMSIX, IOService)

IOService *PC711EarlyMSIX::probe(IOService *provider, SInt32 *) {
if (getKernelVersion() > KernelVersion::Ventura)
return nullptr;

auto pci = OSDynamicCast(IOPCIDevice, provider);
if (!isPC711Device(pci))
return nullptr;

const auto result = configurePC711MSIX(pci);
pci->setProperty("PC711CompatEarlyMSIXRequested", true);
pci->setProperty("PC711CompatEarlyConfigureInterruptsResult",
static_cast<unsigned long long>(static_cast<uint32_t>(result)), 32);
SYSLOG("probe", "early PC711 MSI-X request completed: %x",
static_cast<uint32_t>(result));
return nullptr;
}

namespace {

// CreateDeviceInterrupt is exported on Darwin 23-24 but stripped from the
// Darwin 20-22 symbol table. This stable function prologue identifies the
// older implementation without tying the route to a fixed load address.
constexpr uint8_t kLegacyCreateDeviceInterruptPattern[] {
0x55, 0x48, 0x89, 0xE5, 0x41, 0x57, 0x41, 0x56,
0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xEC,
0x18, 0x49, 0x89, 0xCD, 0x49, 0x89, 0xD6, 0x49,
0x89, 0xF7, 0x49, 0x89, 0xFC, 0x48, 0x8D, 0x55,
0xD4, 0xC7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x48,
0x8B, 0x01
};

constexpr const char *kCreateDeviceInterruptSymbol {
"__ZN16IONVMeController21CreateDeviceInterruptEPFvP8OSObjectP22IOInterruptEventSourceiEPFbS1_P28IOFilterInterruptEventSourceEP9IOService"
};
Expand All @@ -33,15 +108,13 @@ class PC711ProbePlugin {
using CreateDeviceInterrupt = IOFilterInterruptEventSource *(*)(void *controller,
IOInterruptEventAction action, IOFilterInterruptAction filter,
IOService *provider);

static void processKext(void *context, KernelPatcher &patcher, size_t index,
mach_vm_address_t address, size_t size);
static IOFilterInterruptEventSource *wrapCreateDeviceInterrupt(void *controller,
IOInterruptEventAction action, IOFilterInterruptAction filter,
IOService *provider);

bool isPC711(IOService *controller, IOPCIDevice *&pci) const;

CreateDeviceInterrupt originalCreateDeviceInterrupt {nullptr};

const char *kextPath {
Expand All @@ -60,13 +133,6 @@ class PC711ProbePlugin {

PC711ProbePlugin plugin;

// Darwin 25 calls this before IONVMeFamily enumerates interrupt sources.
// Reuse the exported IOPCIFamily implementation on older kernels.
extern "C" IOReturn IOPCIDeviceConfigureInterrupts(IOPCIDevice *device,
uint32_t interruptType, uint32_t numRequired, uint32_t numRequested,
IOOptionBits options)
__asm("__ZN11IOPCIDevice19configureInterruptsEjjjj");

PC711ProbePlugin &PC711ProbePlugin::globalPlugin() {
return plugin;
}
Expand All @@ -80,11 +146,7 @@ bool PC711ProbePlugin::isPC711(IOService *controller, IOPCIDevice *&pci) const {
if (!pci)
return false;

const auto vendor = pci->configRead16(kIOPCIConfigVendorID);
const auto device = pci->configRead16(kIOPCIConfigDeviceID);
const auto classRevision = pci->configRead32(kIOPCIConfigRevisionID);
return vendor == kPC711Vendor && device == kPC711Device &&
(classRevision & kNvmeClassRevisionMask) == kNvmeClassRevisionValue;
return isPC711Device(pci);
}

IOFilterInterruptEventSource *PC711ProbePlugin::wrapCreateDeviceInterrupt(
Expand All @@ -100,8 +162,7 @@ IOFilterInterruptEventSource *PC711ProbePlugin::wrapCreateDeviceInterrupt(
filter, provider) : nullptr;
}

const auto result = IOPCIDeviceConfigureInterrupts(pci,
kInterruptTypeMSIX, 1, 1, 0);
const auto result = configurePC711MSIX(pci);
pci->setProperty("PC711CompatMSIXRequested", true);
pci->setProperty("PC711CompatConfigureInterruptsResult",
static_cast<unsigned long long>(static_cast<uint32_t>(result)), 32);
Expand All @@ -110,15 +171,19 @@ IOFilterInterruptEventSource *PC711ProbePlugin::wrapCreateDeviceInterrupt(
instance.originalCreateDeviceInterrupt(controllerPointer, action,
filter, provider) : nullptr;

auto flags = reinterpret_cast<volatile uint8_t *>(controllerPointer) +
kControllerFlagsOffset;
const uint8_t flagsBefore = *flags;
*flags = static_cast<uint8_t>(flagsBefore & ~kLegacyMSIXPathFlag);
const uint8_t flagsAfter = *flags;
pci->setProperty("PC711CompatLegacyMSIXFlagBefore",
static_cast<unsigned long long>(flagsBefore), 8);
pci->setProperty("PC711CompatLegacyMSIXFlagAfter",
static_cast<unsigned long long>(flagsAfter), 8);
uint8_t flagsBefore {0};
uint8_t flagsAfter {0};
if (getKernelVersion() >= KernelVersion::Sonoma) {
auto flags = reinterpret_cast<volatile uint8_t *>(controllerPointer) +
kControllerFlagsOffset;
flagsBefore = *flags;
*flags = static_cast<uint8_t>(flagsBefore & ~kLegacyMSIXPathFlag);
flagsAfter = *flags;
pci->setProperty("PC711CompatLegacyMSIXFlagBefore",
static_cast<unsigned long long>(flagsBefore), 8);
pci->setProperty("PC711CompatLegacyMSIXFlagAfter",
static_cast<unsigned long long>(flagsAfter), 8);
}
pci->setProperty("PC711CompatEventSourceCreated", eventSource != nullptr);

SYSLOG("probe", "PC711 interrupt compatibility applied; configure=%x flags=%02x->%02x event=%d",
Expand All @@ -128,15 +193,29 @@ IOFilterInterruptEventSource *PC711ProbePlugin::wrapCreateDeviceInterrupt(
}

void PC711ProbePlugin::processKext(void *context, KernelPatcher &patcher,
size_t index, mach_vm_address_t, size_t) {
size_t index, mach_vm_address_t address, size_t size) {
auto instance = static_cast<PC711ProbePlugin *>(context);
if (!instance || index != instance->kextInfo.loadIndex)
return;

KernelPatcher::RouteRequest request {
kCreateDeviceInterruptSymbol, wrapCreateDeviceInterrupt,
nullptr, wrapCreateDeviceInterrupt,
instance->originalCreateDeviceInterrupt
};

if (getKernelVersion() >= KernelVersion::Sonoma) {
request.symbol = kCreateDeviceInterruptSymbol;
} else {
size_t offset {0};
if (!KernelPatcher::findPattern(kLegacyCreateDeviceInterruptPattern,
nullptr, sizeof(kLegacyCreateDeviceInterruptPattern),
reinterpret_cast<const void *>(address), size, &offset)) {
SYSLOG("probe", "failed to locate legacy CreateDeviceInterrupt");
return;
}
request.from = address + offset;
}

if (!patcher.routeMultiple(index, &request, 1)) {
SYSLOG("probe", "failed to install PC711 interrupt compatibility route");
return;
Expand Down Expand Up @@ -166,7 +245,7 @@ PluginConfiguration ADDPR(config) {
arrsize(bootargDebug),
nullptr,
0,
KernelVersion::Tiger,
KernelVersion::BigSur,
KernelVersion::Sequoia,
[]() {
PC711ProbePlugin::globalPlugin().init();
Expand Down
103 changes: 73 additions & 30 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,30 +1,73 @@
BSD 3-Clause License

Copyright (c) 2026, StationK9
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
# PolyForm Noncommercial License 1.0.0

<https://polyformproject.org/licenses/noncommercial/1.0.0>

## Acceptance

In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.

## Copyright License

The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).

## Distribution License

The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).

## Notices

You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:

> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)

## Changes and New Works License

The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.

## Patent License

The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.

## Noncommercial Purposes

Any noncommercial purpose is a permitted purpose.

## Personal Uses

Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.

## Noncommercial Organizations

Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.

## Fair Use

You may have "fair use" rights for the software under the law. These terms do not limit them.

## No Other Rights

These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.

## Patent Defense

If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.

## Violations

The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.

## No Liability

***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***

## Definitions

The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.

**You** refers to the individual or entity agreeing to these terms.

**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.

**Your licenses** are all the licenses granted to you for the software under these terms.

**Use** means anything you do with the software requiring one of your licenses.
Loading
Loading