diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ac4b0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Gcc Patch +/*.gcno + +.idea/ +.DS_Store +._DS_Store +__private/ +MacKernelSDK/ diff --git a/Docs/FAQ.md b/Docs/FAQ.md new file mode 100644 index 0000000..827796d --- /dev/null +++ b/Docs/FAQ.md @@ -0,0 +1,73 @@ +# Frequently Asked Questions + +**Last Updated:** Jun 18, 2021 + +## Device Support + +### How do I know if my card reader is supported? + +You can find a list of supported card readers at the [front page](../README.md). +If the support status says `Yes`, your card reader is supported by this driver. + +### What if my card reader is not listed in the table? + +It means that your card reader is not supported by the original Linux driver module `rtsx_pci`. +Since this driver is based on that module, your card reader is not and probably will not be supported by this driver. + +### My card reader is listed in the table but is not supported yet, when are you going to implement it? + +Unfortunately, I don't have an ETA at this moment. +However, it should be relatively easy to add support for other card readers, because the base controller class contains most of the common code and a rich set of APIs to manipulate the registers. +Please note that I am not paid to write this driver and I have other work to do, so I cannot dedicate all my time to this project. +If you would like to support me and this project, please consider a donation. + +### What is the roadmap of this project? + +Milestones: +- 1: Base controller implementation for all supported Realtek PCIe-based card readers. +- 2: Chip-independent Realtek SD host device implementation. +- 3: Initial SD host driver stack implementation for macOS. +- 4: I/O performance and stability of the driver. (<-- Right Now) +- 5: Power management implementation. +- 6: Support more card readers. + +## Installation + +### How do I install this driver? + +If you are using OpenCore, refer to the guide provided by [Dortania](https://dortania.github.io/getting-started/). +If you are using Clover, place the driver under the `kexts/` folder. + +### How do I know if the driver is loaded? + +Run the following command in your terminal. + +```(sh) +kextstat | grep rtsx +``` + +If you see `science.firewolf.rtsx` in your terminal, then the driver is loaded. +As a bonus, you should be able to see your card reader recognized as a native one in **System Information**. + +### How do I dump the log produced by the driver? + +Make sure that you are using the DEBUG version of the driver and have added the boot argument `msgbuf=10485760`. (Note that there is an extra zero at the end of `1048576`.) + +Run the following command in your terminal. + +```(sh) +sudo dmesg | grep RTSX > ~/Desktop/rtsx.log +``` + +You can now find the log on your `Desktop` folder. +If the log file is empty, please install the [DebugEnhancer](https://github.com/acidanthera/DebugEnhancer) and try again. + +### What is the difference between the DEBUG and the RELEASE version? + +The DEBUG version produces a massive amount of log that records accesses to some key registers and thus slows down the boot process and I/O transactions. +However, you are recommended to use the DEBUG version at first to ensure that the driver is working properly with your hardware. +You should always attach the log produced by the DEBUG version when you are asking for help on the forum and/or filing an issue on Github. +Once everything is fine, you may switch to the RELEASE version. + +The RELEASE version produces log only when there is an error and thus may not be helpful when you plan to investigate an issue. +However, you are recommended to use the RELEASE version after you ensure that the driver works fine with your hardware to enjoy the best performance. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9860be2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, FireWolf @ FireWolf Pl. +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab12ba7 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# Realtek PCIe Card Reader Driver for macOS +Unleash the full potential of your SDXC UHS-I cards + +## Introduction + +An unofficial macOS kernel extension for Realtek PCIe-based SD card readers. +It uses the Linux driver as a reference implementation but is written from scratch and carefully designed for macOS to deliver the best performance. + +## Features +- No compatibility layer +- Supports SDSC/HC/XC cards +- Supports the default and the high speed modes +- Supports UHS-I SDR12/25/50/104 and DDR50 modes +- Recognizable as a built-in card reader device +- Device-independent SD host driver stack + +## Limitations +- MMC cards are not supported +- SD Express cards are not supported + +## Current Status +- **Last Updated:** Jun 18, 2021 +- **Reference:** [Linux Kernel 5.11](https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.11.tar.xz) +- **Driver Status:** Pre-1.0 Beta + - Power management is not implemented. + - I/O performance and the overall driver stability are of the most concern at this moment. + +## Supported Systems +- macOS Big Sur +- macOS Catalina + +#### Notes: +- Other systems are not tested yet. + +## Supported Devices +| Series | PCI ID | Name | Supported | +|:------:|:----------:|:--------------------------------:|:---------:| +| 5209 | 0x10EC5209 | RTS5209 PCI Express Card Reader | Not Yet | +| 5227 | 0x10EC5227 | RTS5227 PCI Express Card Reader | Not Yet | +| 5227 | 0x10EC522A | RTS522A PCI Express Card Reader | Not Yet | +| 5228 | 0x10EC5228 | RTS5228 PCI Express Card Reader | Not Yet | +| 5229 | 0x10EC5229 | RTS5229 PCI Express Card Reader | Not Yet | +| 5249 | 0x10EC5249 | RTS5249 PCI Express Card Reader | Yes | +| 5249 | 0x10EC524A | RTS524A PCI Express Card Reader | Yes | +| 5249 | 0x10EC525A | RTS525A PCI Express Card Reader | Yes | +| 5260 | 0x10EC5260 | RTS5260 PCI Express Card Reader | Not Yet | +| 5261 | 0x10EC5261 | RTS5261 PCI Express Card Reader | Not Yet | +| 8411 | 0x10EC5286 | RTL8402 PCI Express Card Reader | Not Yet | +| 8411 | 0x10EC5287 | RTL8411B PCI Express Card Reader | Not Yet | +| 8411 | 0x10EC5289 | RTL8411 PCI Express Card Reader | Not Yet | + +#### Notes: +- By design, all listed devices are supported, and devices that have the same series share most of the controller code. +- RTS525A has the highest priority than other chips, because that's the only chip available for me to test the driver. +- If a device's support status is "Not Yet", its controller is not implemented yet. + +## Questions, Issues and Documentation + +### Users +Please read [FAQs](Docs/FAQ.md) carefully before asking any questions. +Please use the issue template before submitting any code-level issues. +Please clearly indicate your chip model, device ID and revision number and attach the kernel log in your issue. + +### Developers +You are welcome to submit pull requests to improve this driver. +Please read the code documentation to understand how each class/function is related to the Linux driver code and how to add support for other listed devices. +A detailed port note will be available later. + +## Support +Writing a driver from scratch is hard and time consuming. +If you would like to support my work, please consider a donation. + +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate/?business=M6AHXMUVSZQTS&no_recurring=0&item_name=Support+Realtek+PCIe+card+deader+driver+for+macOS¤cy_code=USD) + + +## References +- [SD Physical Layer Simplified Specification](https://www.sdcard.org/downloads/pls/) + +## Credits +- [Acidanthera](https://github.com/acidanthera) for [MacKernelSDK](https://github.com/acidanthera/MacKernelSDK) +- [Realtek](https://www.realtek.com/) for the Linux [RTSX PCI](https://github.com/torvalds/linux/tree/master/drivers/misc/cardreader) driver +- [FireWolf](https://github.com/0xFireWolf) for developing the card reader driver for macOS + +## License +This project is licensed under BSD-3-Clause. +Copyright (C) 2021 FireWolf @ FireWolf Pl. All Rights Reserved. diff --git a/RealtekPCIeCardReader.xcodeproj/project.pbxproj b/RealtekPCIeCardReader.xcodeproj/project.pbxproj new file mode 100644 index 0000000..143b3db --- /dev/null +++ b/RealtekPCIeCardReader.xcodeproj/project.pbxproj @@ -0,0 +1,662 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + D51D6F9C2660B7E100871FA3 /* SD.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D51D6F9A2660B7E100871FA3 /* SD.hpp */; }; + D51D6F9F2660BE3800871FA3 /* RealtekSDXCSlot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D51D6F9D2660BE3800871FA3 /* RealtekSDXCSlot.cpp */; }; + D51D6FA02660BE3800871FA3 /* RealtekSDXCSlot.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D51D6F9E2660BE3800871FA3 /* RealtekSDXCSlot.hpp */; }; + D57B48B725EB08B1000D3E67 /* Utilities.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D57B48B525EB08B1000D3E67 /* Utilities.hpp */; }; + D57B48BC25EB315C000D3E67 /* RealtekRTS5249Controller.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D57B48BA25EB315C000D3E67 /* RealtekRTS5249Controller.cpp */; }; + D57B48BD25EB315C000D3E67 /* RealtekRTS5249Controller.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D57B48BB25EB315C000D3E67 /* RealtekRTS5249Controller.hpp */; }; + D57FA69B267B0BB00023097C /* IOSDSimpleBlockRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D57FA699267B0BB00023097C /* IOSDSimpleBlockRequest.cpp */; }; + D57FA69C267B0BB00023097C /* IOSDSimpleBlockRequest.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D57FA69A267B0BB00023097C /* IOSDSimpleBlockRequest.hpp */; }; + D57FA69F267B0BBE0023097C /* IOSDComplexBlockRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D57FA69D267B0BBE0023097C /* IOSDComplexBlockRequest.cpp */; }; + D57FA6A0267B0BBE0023097C /* IOSDComplexBlockRequest.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D57FA69E267B0BBE0023097C /* IOSDComplexBlockRequest.hpp */; }; + D57FA6A5267C1F340023097C /* RealtekRTS5209Controller.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D57FA6A3267C1F340023097C /* RealtekRTS5209Controller.cpp */; }; + D57FA6A6267C1F340023097C /* RealtekRTS5209Controller.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D57FA6A4267C1F340023097C /* RealtekRTS5209Controller.hpp */; }; + D57FA6A9267D2C8D0023097C /* RealtekRTS524AController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D57FA6A7267D2C8D0023097C /* RealtekRTS524AController.cpp */; }; + D57FA6AA267D2C8D0023097C /* RealtekRTS524AController.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D57FA6A8267D2C8D0023097C /* RealtekRTS524AController.hpp */; }; + D57FA6AD267D50400023097C /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = D57FA6AC267D50400023097C /* LICENSE */; }; + D59B34B42651C23F004C3348 /* RealtekRTS5249SeriesController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D59B34B22651C23F004C3348 /* RealtekRTS5249SeriesController.cpp */; }; + D59B34B52651C23F004C3348 /* RealtekRTS5249SeriesController.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59B34B32651C23F004C3348 /* RealtekRTS5249SeriesController.hpp */; }; + D59E076326646BD7009E96EE /* RealtekSDRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D59E076126646BD7009E96EE /* RealtekSDRequest.cpp */; }; + D59E076426646BD7009E96EE /* RealtekSDRequest.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E076226646BD7009E96EE /* RealtekSDRequest.hpp */; }; + D59E076826646BE3009E96EE /* RealtekSDCommand.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E076626646BE3009E96EE /* RealtekSDCommand.hpp */; }; + D59E076C2664B53E009E96EE /* IOSDBusConfig.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E076A2664B53E009E96EE /* IOSDBusConfig.hpp */; }; + D59E077726675FB9009E96EE /* IOSDHostDriver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D59E077526675FB9009E96EE /* IOSDHostDriver.cpp */; }; + D59E077826675FB9009E96EE /* IOSDHostDriver.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E077626675FB9009E96EE /* IOSDHostDriver.hpp */; }; + D59E077B266841FA009E96EE /* IOSDBlockStorageDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D59E0779266841FA009E96EE /* IOSDBlockStorageDevice.cpp */; }; + D59E077C266841FA009E96EE /* IOSDBlockStorageDevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E077A266841FA009E96EE /* IOSDBlockStorageDevice.hpp */; }; + D59E077F2669F153009E96EE /* IOSDCard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D59E077D2669F153009E96EE /* IOSDCard.cpp */; }; + D59E07802669F153009E96EE /* IOSDCard.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E077E2669F153009E96EE /* IOSDCard.hpp */; }; + D59E0783266C09E0009E96EE /* IOSDHostDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D59E0781266C09E0009E96EE /* IOSDHostDevice.cpp */; }; + D59E0784266C09E0009E96EE /* IOSDHostDevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E0782266C09E0009E96EE /* IOSDHostDevice.hpp */; }; + D59E078E266C0FD3009E96EE /* ClosedRange.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E078D266C0FD3009E96EE /* ClosedRange.hpp */; }; + D59E0791266DF6B5009E96EE /* IOSDBlockRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D59E078F266DF6B5009E96EE /* IOSDBlockRequest.cpp */; }; + D59E0792266DF6B5009E96EE /* IOSDBlockRequest.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D59E0790266DF6B5009E96EE /* IOSDBlockRequest.hpp */; }; + D5D2EE2E25DE0212004B5310 /* libkmod.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D5D2EE2D25DE01FD004B5310 /* libkmod.a */; }; + D5D2EE3625DE0358004B5310 /* RealtekRTS525AController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D5D2EE3425DE0358004B5310 /* RealtekRTS525AController.cpp */; }; + D5D2EE3725DE0358004B5310 /* RealtekRTS525AController.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5D2EE3525DE0358004B5310 /* RealtekRTS525AController.hpp */; }; + D5D2EE7525DE4714004B5310 /* Debug.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5D2EE7425DE4714004B5310 /* Debug.hpp */; }; + D5D2EE7B25DE4CE1004B5310 /* Registers.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5D2EE7925DE4CE1004B5310 /* Registers.hpp */; }; + D5D2EE8825DF1316004B5310 /* RealtekPCIeCardReaderController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D5D2EE8625DF1316004B5310 /* RealtekPCIeCardReaderController.cpp */; }; + D5D2EE8925DF1316004B5310 /* RealtekPCIeCardReaderController.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5D2EE8725DF1316004B5310 /* RealtekPCIeCardReaderController.hpp */; }; + D5D2EE9F25E0B639004B5310 /* BitOptions.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5D2EE9E25E0B639004B5310 /* BitOptions.hpp */; }; + D5FF56422670B1C500B0143E /* IOSDBlockRequestQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D5FF56402670B1C500B0143E /* IOSDBlockRequestQueue.cpp */; }; + D5FF56432670B1C500B0143E /* IOSDBlockRequestQueue.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5FF56412670B1C500B0143E /* IOSDBlockRequestQueue.hpp */; }; + D5FF56462671484600B0143E /* IOSDBlockRequestEventSource.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D5FF56442671484600B0143E /* IOSDBlockRequestEventSource.cpp */; }; + D5FF56472671484600B0143E /* IOSDBlockRequestEventSource.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5FF56452671484600B0143E /* IOSDBlockRequestEventSource.hpp */; }; + D5FF564B26715FBE00B0143E /* IOSDCardEventSource.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D5FF564926715FBE00B0143E /* IOSDCardEventSource.cpp */; }; + D5FF564C26715FBE00B0143E /* IOSDCardEventSource.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5FF564A26715FBE00B0143E /* IOSDCardEventSource.hpp */; }; + D5FF564F267221B100B0143E /* AppleSDXCSlot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D5FF564D267221B100B0143E /* AppleSDXCSlot.cpp */; }; + D5FF5650267221B100B0143E /* AppleSDXCSlot.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5FF564E267221B100B0143E /* AppleSDXCSlot.hpp */; }; + D5FF5653267225D800B0143E /* AppleSDXC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D5FF5651267225D800B0143E /* AppleSDXC.cpp */; }; + D5FF5654267225D800B0143E /* AppleSDXC.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5FF5652267225D800B0143E /* AppleSDXC.hpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + D51D6F9A2660B7E100871FA3 /* SD.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SD.hpp; sourceTree = ""; }; + D51D6F9D2660BE3800871FA3 /* RealtekSDXCSlot.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtekSDXCSlot.cpp; sourceTree = ""; }; + D51D6F9E2660BE3800871FA3 /* RealtekSDXCSlot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekSDXCSlot.hpp; sourceTree = ""; }; + D57B48B525EB08B1000D3E67 /* Utilities.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Utilities.hpp; sourceTree = ""; }; + D57B48BA25EB315C000D3E67 /* RealtekRTS5249Controller.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtekRTS5249Controller.cpp; sourceTree = ""; }; + D57B48BB25EB315C000D3E67 /* RealtekRTS5249Controller.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekRTS5249Controller.hpp; sourceTree = ""; }; + D57FA699267B0BB00023097C /* IOSDSimpleBlockRequest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDSimpleBlockRequest.cpp; sourceTree = ""; }; + D57FA69A267B0BB00023097C /* IOSDSimpleBlockRequest.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDSimpleBlockRequest.hpp; sourceTree = ""; }; + D57FA69D267B0BBE0023097C /* IOSDComplexBlockRequest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDComplexBlockRequest.cpp; sourceTree = ""; }; + D57FA69E267B0BBE0023097C /* IOSDComplexBlockRequest.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDComplexBlockRequest.hpp; sourceTree = ""; }; + D57FA6A1267C007A0023097C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; + D57FA6A3267C1F340023097C /* RealtekRTS5209Controller.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtekRTS5209Controller.cpp; sourceTree = ""; }; + D57FA6A4267C1F340023097C /* RealtekRTS5209Controller.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekRTS5209Controller.hpp; sourceTree = ""; }; + D57FA6A7267D2C8D0023097C /* RealtekRTS524AController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtekRTS524AController.cpp; sourceTree = ""; }; + D57FA6A8267D2C8D0023097C /* RealtekRTS524AController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekRTS524AController.hpp; sourceTree = ""; }; + D57FA6AB267D3C610023097C /* FAQ.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = FAQ.md; sourceTree = ""; }; + D57FA6AC267D50400023097C /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; }; + D59B34B22651C23F004C3348 /* RealtekRTS5249SeriesController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtekRTS5249SeriesController.cpp; sourceTree = ""; }; + D59B34B32651C23F004C3348 /* RealtekRTS5249SeriesController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekRTS5249SeriesController.hpp; sourceTree = ""; }; + D59E076126646BD7009E96EE /* RealtekSDRequest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtekSDRequest.cpp; sourceTree = ""; }; + D59E076226646BD7009E96EE /* RealtekSDRequest.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekSDRequest.hpp; sourceTree = ""; }; + D59E076626646BE3009E96EE /* RealtekSDCommand.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekSDCommand.hpp; sourceTree = ""; }; + D59E076A2664B53E009E96EE /* IOSDBusConfig.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDBusConfig.hpp; sourceTree = ""; }; + D59E077526675FB9009E96EE /* IOSDHostDriver.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDHostDriver.cpp; sourceTree = ""; }; + D59E077626675FB9009E96EE /* IOSDHostDriver.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDHostDriver.hpp; sourceTree = ""; }; + D59E0779266841FA009E96EE /* IOSDBlockStorageDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDBlockStorageDevice.cpp; sourceTree = ""; }; + D59E077A266841FA009E96EE /* IOSDBlockStorageDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDBlockStorageDevice.hpp; sourceTree = ""; }; + D59E077D2669F153009E96EE /* IOSDCard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDCard.cpp; sourceTree = ""; }; + D59E077E2669F153009E96EE /* IOSDCard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDCard.hpp; sourceTree = ""; }; + D59E0781266C09E0009E96EE /* IOSDHostDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDHostDevice.cpp; sourceTree = ""; }; + D59E0782266C09E0009E96EE /* IOSDHostDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDHostDevice.hpp; sourceTree = ""; }; + D59E078D266C0FD3009E96EE /* ClosedRange.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClosedRange.hpp; sourceTree = ""; }; + D59E078F266DF6B5009E96EE /* IOSDBlockRequest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDBlockRequest.cpp; sourceTree = ""; }; + D59E0790266DF6B5009E96EE /* IOSDBlockRequest.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDBlockRequest.hpp; sourceTree = ""; }; + D5D2EE1925DE007E004B5310 /* RealtekPCIeCardReader.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealtekPCIeCardReader.kext; sourceTree = BUILT_PRODUCTS_DIR; }; + D5D2EE2025DE007E004B5310 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D5D2EE2D25DE01FD004B5310 /* libkmod.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libkmod.a; path = MacKernelSDK/Library/x86_64/libkmod.a; sourceTree = ""; }; + D5D2EE3425DE0358004B5310 /* RealtekRTS525AController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtekRTS525AController.cpp; sourceTree = ""; }; + D5D2EE3525DE0358004B5310 /* RealtekRTS525AController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekRTS525AController.hpp; sourceTree = ""; }; + D5D2EE7425DE4714004B5310 /* Debug.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Debug.hpp; sourceTree = ""; }; + D5D2EE7925DE4CE1004B5310 /* Registers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Registers.hpp; sourceTree = ""; }; + D5D2EE8625DF1316004B5310 /* RealtekPCIeCardReaderController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtekPCIeCardReaderController.cpp; sourceTree = ""; }; + D5D2EE8725DF1316004B5310 /* RealtekPCIeCardReaderController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealtekPCIeCardReaderController.hpp; sourceTree = ""; }; + D5D2EE9E25E0B639004B5310 /* BitOptions.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BitOptions.hpp; sourceTree = ""; }; + D5FF56402670B1C500B0143E /* IOSDBlockRequestQueue.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDBlockRequestQueue.cpp; sourceTree = ""; }; + D5FF56412670B1C500B0143E /* IOSDBlockRequestQueue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDBlockRequestQueue.hpp; sourceTree = ""; }; + D5FF56442671484600B0143E /* IOSDBlockRequestEventSource.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDBlockRequestEventSource.cpp; sourceTree = ""; }; + D5FF56452671484600B0143E /* IOSDBlockRequestEventSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDBlockRequestEventSource.hpp; sourceTree = ""; }; + D5FF564926715FBE00B0143E /* IOSDCardEventSource.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IOSDCardEventSource.cpp; sourceTree = ""; }; + D5FF564A26715FBE00B0143E /* IOSDCardEventSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IOSDCardEventSource.hpp; sourceTree = ""; }; + D5FF564D267221B100B0143E /* AppleSDXCSlot.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleSDXCSlot.cpp; sourceTree = ""; }; + D5FF564E267221B100B0143E /* AppleSDXCSlot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleSDXCSlot.hpp; sourceTree = ""; }; + D5FF5651267225D800B0143E /* AppleSDXC.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleSDXC.cpp; sourceTree = ""; }; + D5FF5652267225D800B0143E /* AppleSDXC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleSDXC.hpp; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D5D2EE1625DE007E004B5310 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D5D2EE2E25DE0212004B5310 /* libkmod.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D51D6FA526622C4800871FA3 /* Controllers */ = { + isa = PBXGroup; + children = ( + D51D6FA826622CA000871FA3 /* Base */, + D5D2EE9425DF1C5A004B5310 /* RTL8411 */, + D5D2EE8E25DF1C2B004B5310 /* RTS5209 */, + D5D2EE8F25DF1C37004B5310 /* RTS5227 */, + D5D2EE9025DF1C40004B5310 /* RTS5228 */, + D5D2EE9125DF1C44004B5310 /* RTS5229 */, + D5D2EE8D25DF1C0D004B5310 /* RTS5249 */, + D5D2EE9225DF1C4A004B5310 /* RTS5260 */, + D5D2EE9325DF1C55004B5310 /* RTS5261 */, + ); + name = Controllers; + sourceTree = ""; + }; + D51D6FA626622C4E00871FA3 /* Card Slots */ = { + isa = PBXGroup; + children = ( + D5FF564D267221B100B0143E /* AppleSDXCSlot.cpp */, + D5FF564E267221B100B0143E /* AppleSDXCSlot.hpp */, + D59E0781266C09E0009E96EE /* IOSDHostDevice.cpp */, + D59E0782266C09E0009E96EE /* IOSDHostDevice.hpp */, + D51D6F9D2660BE3800871FA3 /* RealtekSDXCSlot.cpp */, + D51D6F9E2660BE3800871FA3 /* RealtekSDXCSlot.hpp */, + ); + name = "Card Slots"; + sourceTree = ""; + }; + D51D6FA726622C5800871FA3 /* Block Devices */ = { + isa = PBXGroup; + children = ( + D59E0779266841FA009E96EE /* IOSDBlockStorageDevice.cpp */, + D59E077A266841FA009E96EE /* IOSDBlockStorageDevice.hpp */, + ); + name = "Block Devices"; + sourceTree = ""; + }; + D51D6FA826622CA000871FA3 /* Base */ = { + isa = PBXGroup; + children = ( + D5D2EE8625DF1316004B5310 /* RealtekPCIeCardReaderController.cpp */, + D5D2EE8725DF1316004B5310 /* RealtekPCIeCardReaderController.hpp */, + D5FF5651267225D800B0143E /* AppleSDXC.cpp */, + D5FF5652267225D800B0143E /* AppleSDXC.hpp */, + ); + name = Base; + sourceTree = ""; + }; + D51D6FA926622CBC00871FA3 /* Definitions */ = { + isa = PBXGroup; + children = ( + D59E076626646BE3009E96EE /* RealtekSDCommand.hpp */, + D59E076126646BD7009E96EE /* RealtekSDRequest.cpp */, + D59E076226646BD7009E96EE /* RealtekSDRequest.hpp */, + D5D2EE7925DE4CE1004B5310 /* Registers.hpp */, + D51D6F9A2660B7E100871FA3 /* SD.hpp */, + D59E076A2664B53E009E96EE /* IOSDBusConfig.hpp */, + ); + name = Definitions; + sourceTree = ""; + }; + D51D6FAA26622CDC00871FA3 /* Miscellaneous */ = { + isa = PBXGroup; + children = ( + D5D2EE9E25E0B639004B5310 /* BitOptions.hpp */, + D5D2EE7425DE4714004B5310 /* Debug.hpp */, + D57B48B525EB08B1000D3E67 /* Utilities.hpp */, + D59E078D266C0FD3009E96EE /* ClosedRange.hpp */, + ); + name = Miscellaneous; + sourceTree = ""; + }; + D57FA6A2267C07D90023097C /* Docs */ = { + isa = PBXGroup; + children = ( + D57FA6AB267D3C610023097C /* FAQ.md */, + ); + path = Docs; + sourceTree = ""; + }; + D5D2EE0F25DE007E004B5310 = { + isa = PBXGroup; + children = ( + D57FA6A2267C07D90023097C /* Docs */, + D5D2EE1B25DE007E004B5310 /* RealtekPCIeCardReader */, + D5D2EE1A25DE007E004B5310 /* Products */, + D5D2EE2C25DE01FD004B5310 /* Frameworks */, + ); + sourceTree = ""; + }; + D5D2EE1A25DE007E004B5310 /* Products */ = { + isa = PBXGroup; + children = ( + D5D2EE1925DE007E004B5310 /* RealtekPCIeCardReader.kext */, + ); + name = Products; + sourceTree = ""; + }; + D5D2EE1B25DE007E004B5310 /* RealtekPCIeCardReader */ = { + isa = PBXGroup; + children = ( + D51D6FA726622C5800871FA3 /* Block Devices */, + D51D6FA626622C4E00871FA3 /* Card Slots */, + D51D6FA526622C4800871FA3 /* Controllers */, + D51D6FA926622CBC00871FA3 /* Definitions */, + D5FF56482671490300B0143E /* Host Drivers */, + D51D6FAA26622CDC00871FA3 /* Miscellaneous */, + D57FA6A1267C007A0023097C /* README.md */, + D5D2EE2025DE007E004B5310 /* Info.plist */, + D57FA6AC267D50400023097C /* LICENSE */, + ); + path = RealtekPCIeCardReader; + sourceTree = ""; + }; + D5D2EE2C25DE01FD004B5310 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D5D2EE2D25DE01FD004B5310 /* libkmod.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + D5D2EE8D25DF1C0D004B5310 /* RTS5249 */ = { + isa = PBXGroup; + children = ( + D57FA6A7267D2C8D0023097C /* RealtekRTS524AController.cpp */, + D57FA6A8267D2C8D0023097C /* RealtekRTS524AController.hpp */, + D5D2EE3425DE0358004B5310 /* RealtekRTS525AController.cpp */, + D5D2EE3525DE0358004B5310 /* RealtekRTS525AController.hpp */, + D57B48BA25EB315C000D3E67 /* RealtekRTS5249Controller.cpp */, + D57B48BB25EB315C000D3E67 /* RealtekRTS5249Controller.hpp */, + D59B34B22651C23F004C3348 /* RealtekRTS5249SeriesController.cpp */, + D59B34B32651C23F004C3348 /* RealtekRTS5249SeriesController.hpp */, + ); + name = RTS5249; + sourceTree = ""; + }; + D5D2EE8E25DF1C2B004B5310 /* RTS5209 */ = { + isa = PBXGroup; + children = ( + D57FA6A3267C1F340023097C /* RealtekRTS5209Controller.cpp */, + D57FA6A4267C1F340023097C /* RealtekRTS5209Controller.hpp */, + ); + name = RTS5209; + sourceTree = ""; + }; + D5D2EE8F25DF1C37004B5310 /* RTS5227 */ = { + isa = PBXGroup; + children = ( + ); + name = RTS5227; + sourceTree = ""; + }; + D5D2EE9025DF1C40004B5310 /* RTS5228 */ = { + isa = PBXGroup; + children = ( + ); + name = RTS5228; + sourceTree = ""; + }; + D5D2EE9125DF1C44004B5310 /* RTS5229 */ = { + isa = PBXGroup; + children = ( + ); + name = RTS5229; + sourceTree = ""; + }; + D5D2EE9225DF1C4A004B5310 /* RTS5260 */ = { + isa = PBXGroup; + children = ( + ); + name = RTS5260; + sourceTree = ""; + }; + D5D2EE9325DF1C55004B5310 /* RTS5261 */ = { + isa = PBXGroup; + children = ( + ); + name = RTS5261; + sourceTree = ""; + }; + D5D2EE9425DF1C5A004B5310 /* RTL8411 */ = { + isa = PBXGroup; + children = ( + ); + name = RTL8411; + sourceTree = ""; + }; + D5FF56482671490300B0143E /* Host Drivers */ = { + isa = PBXGroup; + children = ( + D59E077526675FB9009E96EE /* IOSDHostDriver.cpp */, + D59E077626675FB9009E96EE /* IOSDHostDriver.hpp */, + D59E078F266DF6B5009E96EE /* IOSDBlockRequest.cpp */, + D59E0790266DF6B5009E96EE /* IOSDBlockRequest.hpp */, + D57FA699267B0BB00023097C /* IOSDSimpleBlockRequest.cpp */, + D57FA69A267B0BB00023097C /* IOSDSimpleBlockRequest.hpp */, + D57FA69D267B0BBE0023097C /* IOSDComplexBlockRequest.cpp */, + D57FA69E267B0BBE0023097C /* IOSDComplexBlockRequest.hpp */, + D5FF56402670B1C500B0143E /* IOSDBlockRequestQueue.cpp */, + D5FF56412670B1C500B0143E /* IOSDBlockRequestQueue.hpp */, + D5FF56442671484600B0143E /* IOSDBlockRequestEventSource.cpp */, + D5FF56452671484600B0143E /* IOSDBlockRequestEventSource.hpp */, + D5FF564926715FBE00B0143E /* IOSDCardEventSource.cpp */, + D5FF564A26715FBE00B0143E /* IOSDCardEventSource.hpp */, + D59E077D2669F153009E96EE /* IOSDCard.cpp */, + D59E077E2669F153009E96EE /* IOSDCard.hpp */, + ); + name = "Host Drivers"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D5D2EE1425DE007E004B5310 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D5D2EE7B25DE4CE1004B5310 /* Registers.hpp in Headers */, + D57FA6A6267C1F340023097C /* RealtekRTS5209Controller.hpp in Headers */, + D59E0784266C09E0009E96EE /* IOSDHostDevice.hpp in Headers */, + D5D2EE7525DE4714004B5310 /* Debug.hpp in Headers */, + D5FF564C26715FBE00B0143E /* IOSDCardEventSource.hpp in Headers */, + D5FF56432670B1C500B0143E /* IOSDBlockRequestQueue.hpp in Headers */, + D59E0792266DF6B5009E96EE /* IOSDBlockRequest.hpp in Headers */, + D59E076426646BD7009E96EE /* RealtekSDRequest.hpp in Headers */, + D51D6F9C2660B7E100871FA3 /* SD.hpp in Headers */, + D5D2EE9F25E0B639004B5310 /* BitOptions.hpp in Headers */, + D5D2EE3725DE0358004B5310 /* RealtekRTS525AController.hpp in Headers */, + D59E077C266841FA009E96EE /* IOSDBlockStorageDevice.hpp in Headers */, + D5FF56472671484600B0143E /* IOSDBlockRequestEventSource.hpp in Headers */, + D51D6FA02660BE3800871FA3 /* RealtekSDXCSlot.hpp in Headers */, + D59E077826675FB9009E96EE /* IOSDHostDriver.hpp in Headers */, + D59B34B52651C23F004C3348 /* RealtekRTS5249SeriesController.hpp in Headers */, + D57FA69C267B0BB00023097C /* IOSDSimpleBlockRequest.hpp in Headers */, + D5FF5654267225D800B0143E /* AppleSDXC.hpp in Headers */, + D59E078E266C0FD3009E96EE /* ClosedRange.hpp in Headers */, + D59E076C2664B53E009E96EE /* IOSDBusConfig.hpp in Headers */, + D59E07802669F153009E96EE /* IOSDCard.hpp in Headers */, + D5D2EE8925DF1316004B5310 /* RealtekPCIeCardReaderController.hpp in Headers */, + D57B48BD25EB315C000D3E67 /* RealtekRTS5249Controller.hpp in Headers */, + D57FA6AA267D2C8D0023097C /* RealtekRTS524AController.hpp in Headers */, + D57FA6A0267B0BBE0023097C /* IOSDComplexBlockRequest.hpp in Headers */, + D57B48B725EB08B1000D3E67 /* Utilities.hpp in Headers */, + D59E076826646BE3009E96EE /* RealtekSDCommand.hpp in Headers */, + D5FF5650267221B100B0143E /* AppleSDXCSlot.hpp in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D5D2EE1825DE007E004B5310 /* RealtekPCIeCardReader */ = { + isa = PBXNativeTarget; + buildConfigurationList = D5D2EE2325DE007E004B5310 /* Build configuration list for PBXNativeTarget "RealtekPCIeCardReader" */; + buildPhases = ( + D5D2EE1425DE007E004B5310 /* Headers */, + D5D2EE1525DE007E004B5310 /* Sources */, + D5D2EE1625DE007E004B5310 /* Frameworks */, + D5D2EE1725DE007E004B5310 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RealtekPCIeCardReader; + productName = RealtekPCIeCardReader; + productReference = D5D2EE1925DE007E004B5310 /* RealtekPCIeCardReader.kext */; + productType = "com.apple.product-type.kernel-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D5D2EE1025DE007E004B5310 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1250; + TargetAttributes = { + D5D2EE1825DE007E004B5310 = { + CreatedOnToolsVersion = 12.4; + }; + }; + }; + buildConfigurationList = D5D2EE1325DE007E004B5310 /* Build configuration list for PBXProject "RealtekPCIeCardReader" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D5D2EE0F25DE007E004B5310; + productRefGroup = D5D2EE1A25DE007E004B5310 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D5D2EE1825DE007E004B5310 /* RealtekPCIeCardReader */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D5D2EE1725DE007E004B5310 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D57FA6AD267D50400023097C /* LICENSE in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D5D2EE1525DE007E004B5310 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D57FA6A5267C1F340023097C /* RealtekRTS5209Controller.cpp in Sources */, + D59E0791266DF6B5009E96EE /* IOSDBlockRequest.cpp in Sources */, + D57FA6A9267D2C8D0023097C /* RealtekRTS524AController.cpp in Sources */, + D5FF5653267225D800B0143E /* AppleSDXC.cpp in Sources */, + D51D6F9F2660BE3800871FA3 /* RealtekSDXCSlot.cpp in Sources */, + D5D2EE8825DF1316004B5310 /* RealtekPCIeCardReaderController.cpp in Sources */, + D59E077F2669F153009E96EE /* IOSDCard.cpp in Sources */, + D5D2EE3625DE0358004B5310 /* RealtekRTS525AController.cpp in Sources */, + D57FA69B267B0BB00023097C /* IOSDSimpleBlockRequest.cpp in Sources */, + D59E0783266C09E0009E96EE /* IOSDHostDevice.cpp in Sources */, + D5FF56462671484600B0143E /* IOSDBlockRequestEventSource.cpp in Sources */, + D5FF564F267221B100B0143E /* AppleSDXCSlot.cpp in Sources */, + D57FA69F267B0BBE0023097C /* IOSDComplexBlockRequest.cpp in Sources */, + D5FF564B26715FBE00B0143E /* IOSDCardEventSource.cpp in Sources */, + D57B48BC25EB315C000D3E67 /* RealtekRTS5249Controller.cpp in Sources */, + D59E076326646BD7009E96EE /* RealtekSDRequest.cpp in Sources */, + D59E077B266841FA009E96EE /* IOSDBlockStorageDevice.cpp in Sources */, + D5FF56422670B1C500B0143E /* IOSDBlockRequestQueue.cpp in Sources */, + D59E077726675FB9009E96EE /* IOSDHostDriver.cpp in Sources */, + D59B34B42651C23F004C3348 /* RealtekRTS5249SeriesController.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + D5D2EE2125DE007E004B5310 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = x86_64; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + KERNEL_EXTENSION_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; + KERNEL_FRAMEWORK_HEADERS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + D5D2EE2225DE007E004B5310 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = x86_64; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + KERNEL_EXTENSION_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; + KERNEL_FRAMEWORK_HEADERS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + D5D2EE2425DE007E004B5310 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = "$(MODULE_VERSION)"; + DEVELOPMENT_TEAM = ""; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = RealtekPCIeCardReader/Info.plist; + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/MacKernelSDK/Library/x86_64"; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = "$(MODULE_VERSION)"; + MODULE_NAME = science.firewolf.RealtekPCIeCardReader; + MODULE_VERSION = 0.9.0; + PRODUCT_BUNDLE_IDENTIFIER = science.firewolf.rtsx; + PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = YES; + WRAPPER_EXTENSION = kext; + }; + name = Debug; + }; + D5D2EE2525DE007E004B5310 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = "$(MODULE_VERSION)"; + DEVELOPMENT_TEAM = ""; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = RealtekPCIeCardReader/Info.plist; + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/MacKernelSDK/Library/x86_64"; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = "$(MODULE_VERSION)"; + MODULE_NAME = science.firewolf.RealtekPCIeCardReader; + MODULE_VERSION = 0.9.0; + PRODUCT_BUNDLE_IDENTIFIER = science.firewolf.rtsx; + PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = YES; + WRAPPER_EXTENSION = kext; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D5D2EE1325DE007E004B5310 /* Build configuration list for PBXProject "RealtekPCIeCardReader" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D5D2EE2125DE007E004B5310 /* Debug */, + D5D2EE2225DE007E004B5310 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D5D2EE2325DE007E004B5310 /* Build configuration list for PBXNativeTarget "RealtekPCIeCardReader" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D5D2EE2425DE007E004B5310 /* Debug */, + D5D2EE2525DE007E004B5310 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D5D2EE1025DE007E004B5310 /* Project object */; +} diff --git a/RealtekPCIeCardReader.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/RealtekPCIeCardReader.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/RealtekPCIeCardReader.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/RealtekPCIeCardReader.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/RealtekPCIeCardReader.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/RealtekPCIeCardReader.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/RealtekPCIeCardReader/AppleSDXC.cpp b/RealtekPCIeCardReader/AppleSDXC.cpp new file mode 100644 index 0000000..a62cd4d --- /dev/null +++ b/RealtekPCIeCardReader/AppleSDXC.cpp @@ -0,0 +1,14 @@ +// +// AppleSDXC.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/10/21. +// + +#include "AppleSDXC.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(AppleSDXC, IOService); diff --git a/RealtekPCIeCardReader/AppleSDXC.hpp b/RealtekPCIeCardReader/AppleSDXC.hpp new file mode 100644 index 0000000..49c7e32 --- /dev/null +++ b/RealtekPCIeCardReader/AppleSDXC.hpp @@ -0,0 +1,30 @@ +// +// AppleSDXC.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/10/21. +// + +#ifndef AppleSDXC_hpp +#define AppleSDXC_hpp + +#include + +/// +/// An empty class from which the card reader controller inherits, +/// so the system profiler can see the card reader and list it. +/// +/// @see /System/Library/SystemProfiler/SPCardReaderReporter.spreporter +/// +class AppleSDXC: public IOService +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareDefaultStructors(AppleSDXC); + + using super = IOService; +}; + +#endif /* AppleSDXC_hpp */ diff --git a/RealtekPCIeCardReader/AppleSDXCSlot.cpp b/RealtekPCIeCardReader/AppleSDXCSlot.cpp new file mode 100644 index 0000000..3b3ed0b --- /dev/null +++ b/RealtekPCIeCardReader/AppleSDXCSlot.cpp @@ -0,0 +1,14 @@ +// +// AppleSDXCSlot.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/10/21. +// + +#include "AppleSDXCSlot.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndAbstractStructors(AppleSDXCSlot, IOSDHostDevice); diff --git a/RealtekPCIeCardReader/AppleSDXCSlot.hpp b/RealtekPCIeCardReader/AppleSDXCSlot.hpp new file mode 100644 index 0000000..e519371 --- /dev/null +++ b/RealtekPCIeCardReader/AppleSDXCSlot.hpp @@ -0,0 +1,30 @@ +// +// AppleSDXCSlot.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/10/21. +// + +#ifndef AppleSDXCSlot_hpp +#define AppleSDXCSlot_hpp + +#include "IOSDHostDevice.hpp" + +/// +/// An empty class from which the host device inherits, +/// so the system profiler can see the card slot and list it. +/// +/// @see /System/Library/SystemProfiler/SPCardReaderReporter.spreporter +/// +class AppleSDXCSlot: public IOSDHostDevice +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareAbstractStructors(AppleSDXCSlot); + + using super = IOSDHostDevice; +}; + +#endif /* AppleSDXCSlot_hpp */ diff --git a/RealtekPCIeCardReader/BitOptions.hpp b/RealtekPCIeCardReader/BitOptions.hpp new file mode 100644 index 0000000..e1a0616 --- /dev/null +++ b/RealtekPCIeCardReader/BitOptions.hpp @@ -0,0 +1,288 @@ +// +// BitOptions.h +// RealtekPCIeCardReader +// +// Created by FireWolf on 2017-11-11. +// Revised by FireWolf on 2019-09-27. +// Adopted by FireWolf on 2021-02-19. (Apple Darwin Kernel) +// +// OSBitOptions, the original name, is part of OSFoundation. +// The header was initially implemented for CPSC 415 Xeros OS. +// BitOptions has now been optimized for C++. +// Copyright © 2017-2020 FireWolf. All rights reserved. +// + +#ifndef BitOptions_h +#define BitOptions_h + +#include +#include "Debug.hpp" + +// +// OSBitOptions - The original header +// +// A lightweight "interface" to represent bit mask types +// Each bit is a member of the options +// +// `OSBitOptions` treats an integer as a bit mask and provides +// useful functions to manipulate individual members. +// +// For example, an `UInt8` type can be used to represent 8 different options. +// The index of the highest bit is 7, whereas the lowest bit is 0. +// +// BINARY: 1 0 0 0 1 1 1 1 +// INDEX: 7 6 5 4 3 2 1 0 +// +// A useful macro `OSBitOptionCreate(index)` takes the bit index as its argument +// and create the corresponding option value. +// + +/// +/// Represents a collection of bit options +/// +/// @note Contains up to sizeof(T) options +/// @note It is recommended to use `uint8_t`, `uint16_t`, `uint32_t` and `uint64_t` as the storage type. +/// If variable size is desired, consider to use `size_t` or `unsigned long` instead. +/// By default, it uses the `size_t` type to best work with both 32-bit and 64-bit platforms. +/// +template +struct BitOptions +{ +private: + /// The internal storage + T options; + +public: + /// An unsigned integer type that represents a single bit option + using BitOption = T; + + /// + /// Create a single bit option with the given bit index + /// + /// @param index The index of the bit + /// @return The BitOption value. + /// + static inline BitOption createOptionWithIndex(size_t index) + { + //passert(index < sizeof(T) * 8, "The given bit index is invalid."); + + return static_cast(1) << index; + } + + /// Create a collection of bit options from the given raw value + /// By default, an empty collection of options is created. + BitOptions(T options = 0) + { + this->options = options; + } + + /// + /// Insert the given option + /// + /// @param option An option to be inserted + /// + inline void mutativeInsert(BitOption option) + { + this->options |= option; + } + + /// + /// Remove the given option + /// + /// @param option An option to be removed + /// + inline void mutativeRemove(BitOption option) + { + this->options &= (~option); + } + + /// + /// Perform the bitwise AND operation with the given option + /// + /// @param option An option to be "and"ed + /// + inline void mutativeBitwiseAnd(BitOption option) + { + this->options &= option; + } + + /// + /// Perform the bitwise OR operation with the given option + /// + /// @param option An option to be "or"ed + /// + inline void mutativeBitwiseOr(BitOption option) + { + this->options |= option; + } + + /// + /// Insert the given option and return the new options + /// + /// @param option An option to be inserted + /// @return The new options after the insertion + /// + [[nodiscard]] + inline BitOptions insert(BitOption option) const + { + return BitOptions(this->options | option); + } + + /// + /// Remove the given option and return the new options + /// + /// @param option An option to be removed + /// @return The new options after the removal + /// + [[nodiscard]] + inline BitOptions remove(BitOption option) const + { + return BitOptions(this->options & (~option)); + } + + /// + /// Perform the bitwise AND operation with the given option and return the new options + /// + /// @param option An option to be "and"ed + /// + [[nodiscard]] + inline BitOptions bitwiseAnd(BitOption option) const + { + return BitOptions(this->options & option); + } + + /// + /// Perform the bitwise OR operation with the given option and return the new options + /// + /// @param option An option to be "or"ed + /// + [[nodiscard]] + inline BitOptions bitwiseOr(BitOption option) const + { + return BitOptions(this->options | option); + } + + /// + /// Check whether the given option in in this collection + /// + /// @param option An option to be checked + /// @return `true` if `this` contains the given `option`, `false` otherwise. + /// + [[nodiscard]] + inline bool contains(BitOption option) const + { + return (this->options & option) == option; + } + + /// + /// Check whether this collection of bit options is empty + /// + /// @return `true` if it is empty, `false` otherwise. + /// + [[nodiscard]] + inline bool isEmpty() const + { + return this->options == 0; + } + + /// + /// Export this collection of options as a single option + /// + /// @return The flatten version of this collection of options. + /// + [[nodiscard]] + inline BitOption flatten() const + { + return this->options; + } + + /// + /// Clear all bits + /// + inline void clearAll() + { + this->options = 0; + } + + /// + /// Set all bits + /// + inline void setAll() + { + this->options = -1; + } + + /// + /// Check whether the bit at the given index is set + /// + /// @param index Index of the bit + /// @return `true` if the target bit is set, `false` otherwise. + /// + [[nodiscard]] + inline bool containsBit(size_t index) const + { + return this->contains(BitOptions::createOptionWithIndex(index)); + } + + /// + /// Get the bit at the given index + /// + /// @param index Index of the bit + /// @return The bit at the given index. + /// + [[nodiscard]] + inline uint8_t getBit(size_t index) const + { + return this->containsBit(index) ? 1 : 0; + } + + /// + /// Set the bit at the given index + /// + /// @param index Index of the bit + /// + inline void setBit(size_t index) + { + this->mutativeInsert(BitOptions::createOptionWithIndex(index)); + } + + /// + /// Clear the bit at the given index + /// + /// @param index Index of the bit + /// + inline void clearBit(size_t index) + { + this->mutativeRemove(BitOptions::createOptionWithIndex(index)); + } + + /// Perform the bitwise AND and the assignment operation + BitOptions& operator&=(const BitOptions& rhs) + { + this->options &= rhs.options; + + return *this; + } + + /// Perform the bitwise OR and the assignment operation + BitOptions& operator|=(const BitOptions& rhs) + { + this->options |= rhs.options; + + return *this; + } + + /// Perform the bitwise AND operation + BitOptions operator&(const BitOptions& rhs) + { + return BitOptions(this->options & rhs.options); + } + + /// Perform the bitwise AND operation + BitOptions operator|(const BitOptions& rhs) + { + return BitOptions(this->options | rhs.options); + } +}; + +#endif /* BitOptions_h */ diff --git a/RealtekPCIeCardReader/ClosedRange.hpp b/RealtekPCIeCardReader/ClosedRange.hpp new file mode 100644 index 0000000..618dd57 --- /dev/null +++ b/RealtekPCIeCardReader/ClosedRange.hpp @@ -0,0 +1,81 @@ +// +// ClosedRange.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 9/25/20. +// Copyright © 2020 FireWolf. All rights reserved. +// + +#ifndef ClosedRange_hpp +#define ClosedRange_hpp + +#include +#include "Debug.hpp" + +/// A structure that describes a closed interval from a lower bound to an upper bound +template +struct ClosedRange +{ + /// The lower bound + Bound lowerBound; + + /// The upper bound + Bound upperBound; + + /// Default constructor + ClosedRange() {} + + /// Create a closed range with the given bounds + ClosedRange(Bound lowerBound, Bound upperBound) + { + passert(lowerBound <= upperBound, "The given bounds are invalid."); + + this->lowerBound = lowerBound; + + this->upperBound = upperBound; + } + + /// + /// Create a closed range with the given length + /// + /// @param start The start position as the lower bound + /// @param length The number of elements in the range + /// @return A closed range that starts at `start` and covers `length` elements. + /// + static inline ClosedRange createWithLength(Bound start, Bound length) + { + return ClosedRange(start, start + length - 1); + } + + /// Get the number of elements in this range + [[nodiscard]] + inline Bound getLength() const + { + return this->upperBound - this->lowerBound + 1; + } + + /// + /// Check whether this range is valid + /// + /// @return `true` if valid, `false` otherwise. + /// + [[nodiscard]] + inline bool isValid() const + { + return this->lowerBound <= this->upperBound; + } + + /// + /// Check whether the range contains the given value + /// + /// @param value The value to be checked + /// @return `true` if the value is within the range, `false` otherwise. + /// + [[nodiscard]] + inline bool contains(Bound value) const + { + return this->lowerBound <= value && value <= this->upperBound; + } +}; + +#endif /* ClosedRange_hpp */ diff --git a/RealtekPCIeCardReader/Debug.hpp b/RealtekPCIeCardReader/Debug.hpp new file mode 100644 index 0000000..e7fe19b --- /dev/null +++ b/RealtekPCIeCardReader/Debug.hpp @@ -0,0 +1,157 @@ +// +// Debug.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/11/20. +// Revised by FireWolf on 6/9/20. +// Adopted by FireWolf on 2/17/21 for Darwin Kernel. +// Copyright © 2020 FireWolf. All rights reserved. +// + +#ifndef Debug_h +#define Debug_h + +#include +#include + +//static void OSLog(const char* format, ...) +//{ +// char buffer[1024]; +// +// buffer[0] = '\0'; +// +// va_list args; +// +// va_start(args, format); +// +// vsnprintf(buffer, sizeof(buffer), format, args); +// +// va_end(args); +// +// if (ml_get_interrupts_enabled()) +// { +// IOLog("%s", buffer); +// +// //IOSleep(10); +// } +//} + +#define OSLog IOLog + +#include + +//#define OSLog(fmt, ...) \ +//os_log(OS_LOG_DEFAULT, fmt, ##__VA_ARGS__); + +#define MODULE "RTSX: " + +#ifndef __cplusplus + #define __PRETTY_FUNCTION__ __func__ +#endif + +#ifdef DEBUG +#define pinfo(fmt, ...) \ +{ \ + OSLog(MODULE "%s DInfo: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__); \ +}; +#else +#define pinfo(fmt, ...) {} +#endif + +#ifdef DEBUG +#define pinfof(fmt, ...) \ +{ \ + OSLog(MODULE "%s DInfo: " fmt, __PRETTY_FUNCTION__, ##__VA_ARGS__); \ +}; +#else +#define pinfof(fmt, ...) {} +#endif + +#define perr(fmt, ...) \ +{ \ + OSLog(MODULE "%s Error: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__); \ +}; + +#define pwarning(fmt, ...) \ +{ \ + OSLog(MODULE "%s Warning: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__); \ +}; + +#define passert(cond, fmt, ...) \ +{ \ + if (!(cond)) \ + { \ + OSLog(MODULE "%s Assertion Failed: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__); \ + panic(MODULE "Assertion triggered in file %s at line %d\n", __FILE__, __LINE__); \ + } \ +}; + +#define psoftassert(cond, fmt, ...) \ +{ \ + if (!(cond)) \ + { \ + OSLog(MODULE "%s Soft Assertion Failed: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__); \ + OSLog(MODULE "Assertion triggered in file %s at line %d\n", __FILE__, __LINE__); \ + } \ +}; + +#define pfatal(fmt, ...) \ +{ \ + panic(MODULE "%s Fatal Error: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__); \ +}; + +static inline void pbufcol(const void* buffer, size_t length) +{ + for (auto index = 0; index < length; index += 1) + { + pinfo("[%02d] 0x%02X", index, reinterpret_cast(buffer)[index]); + } +} + +static inline void pbufrow(const void* buffer, size_t length) +{ + for (auto index = 0; index < length; index += 1) + { + OSLog("%02X", reinterpret_cast(buffer)[index]); + } + + OSLog("\n"); +} + +static inline void pbuf(const void* buffer, size_t length, size_t column = 8) +{ + UInt8* ptr = const_cast(reinterpret_cast(buffer)); + + auto nrows = length / column; + + for (auto row = 0; row < nrows; row += 1) + { + OSLog("RTSX: [%04lu] ", row * column); + + pbufrow(ptr, column); + + ptr += column; + } + + if (nrows * column < length) + { + OSLog("RTSX: [%04lu] ", nrows * column); + + pbufrow(ptr, length - nrows * column); + } +} + +#include + +static inline void pdma(IODMACommand* dma, IOByteCount length, IOByteCount column) +{ + auto buffer = IOMallocZero(length); + + psoftassert(dma->readBytes(0, buffer, length) == length, "Failed to read the DMA contents."); + + pbuf(buffer, length, column); + + IOFree(buffer, length); +} + +#endif /* Debug_h */ diff --git a/RealtekPCIeCardReader/IOSDBlockRequest.cpp b/RealtekPCIeCardReader/IOSDBlockRequest.cpp new file mode 100644 index 0000000..6096188 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBlockRequest.cpp @@ -0,0 +1,14 @@ +// +// IOSDBlockRequest.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/6/21. +// + +#include "IOSDBlockRequest.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndAbstractStructors(IOSDBlockRequest, IOCommand); diff --git a/RealtekPCIeCardReader/IOSDBlockRequest.hpp b/RealtekPCIeCardReader/IOSDBlockRequest.hpp new file mode 100644 index 0000000..b341871 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBlockRequest.hpp @@ -0,0 +1,85 @@ +// +// IOSDBlockRequest.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/6/21. +// + +#ifndef IOSDBlockRequest_hpp +#define IOSDBlockRequest_hpp + +#include +#include + +/// Forward Declaration +class IOSDHostDriver; + +/// Represents an abstract request to access blocks on a SD card +class IOSDBlockRequest: public IOCommand +{ + /// Constructors & Destructors + OSDeclareAbstractStructors(IOSDBlockRequest); + + using super = IOCommand; + +public: + /// The type of the processor routine that services the request + using Processor = IOReturn (*)(IOSDHostDriver*, IOSDBlockRequest*); + + /// + /// Initialize a block request + /// + /// @param driver A non-null host driver that processes the request + /// @param processor A non-null routine to process the request + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblocks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @note This function retains the given host driver. + /// The caller must invoke `IOSDBlockRequest::deinit()` before returning the request to the pool. + /// + virtual void init(IOSDHostDriver* driver, + Processor processor, + IOMemoryDescriptor* buffer, + UInt64 block, UInt64 nblocks, + IOStorageAttributes* attributes, + IOStorageCompletion* completion) = 0; + + /// + /// Deinitialize a block request + /// + /// @note The caller must invoke this function to balance the call of `IOSDBlockRequest::init()`. + /// + virtual void deinit() = 0; + + /// + /// Service the block request + /// + /// @note This function is invoked by the processor work loop to fully service the request. + /// + virtual void service() = 0; + + /// + /// Get the DMA command for data transfer to service the request + /// + /// @note This function is invoked by the processor routine to service the request either fully or partially. + /// + virtual IODMACommand* getDMACommand() = 0; + + /// + /// Get the index of the start block to service the request + /// + /// @note This function is invoked by the processor routine to service the request either fully or partially. + /// + virtual UInt64 getBlockOffset() = 0; + + /// + /// Get the number of blocks to service to service the request + /// + /// @note This function is invoked by the processor routine to service the request either fully or partially. + /// + virtual UInt64 getNumBlocks() = 0; +}; + +#endif /* IOSDBlockRequest_hpp */ diff --git a/RealtekPCIeCardReader/IOSDBlockRequestEventSource.cpp b/RealtekPCIeCardReader/IOSDBlockRequestEventSource.cpp new file mode 100644 index 0000000..186f0ce --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBlockRequestEventSource.cpp @@ -0,0 +1,162 @@ +// +// IOSDBlockRequestEventSource.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/9/21. +// + +#include "IOSDBlockRequestEventSource.hpp" +#include "Debug.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(IOSDBlockRequestEventSource, IOEventSource); + +/// +/// Check whether a SD block request is pending and if so process the request on the workloop +/// +/// @return `true` if the work loop should invoke this function again. +/// i.e. One or more requests are pending after the workloop has processed the current one. +/// +bool IOSDBlockRequestEventSource::checkForWork() +{ + // Invoked by the processor work loop + pinfo("The block request event source is invoked by the processor work loop."); + + // Guard: No work if the event source is disabled + // i.e. The host driver stops processing block requests if the card is removed + if (!this->enabled) + { + pinfo("The event has been disabled. Will not process any pending requests."); + + return false; + } + + // Guard: Check whether a block request is pending + if (this->pendingRequests->isEmpty()) + { + pinfo("The request queue is empty."); + + return false; + } + + // Get a pending request + IOSDBlockRequest* request = this->pendingRequests->dequeueRequest(); + + // The processor workloop is the only thread that removes a request from the queue + // Guard: The pending request should be non-null + if (request == nullptr) + { + perr("Detected an inconsistency: The request should not be NULL at this moment."); + + return false; + } + + // Process the request + pinfo("Processing the block request..."); + + request->service(); + + pinfo("The block request has been processed."); + + // Notify the host driver that a block request has been processed + // so that the driver can finalize the request and return it to the pool + passert(this->action != nullptr, "The completion routine should not be NULL."); + + (*this->action)(this->owner, request); + + // The request has been processed + // Guard: Check whether the host driver has stopped processing pending requests + if (!this->enabled) + { + pinfo("The event has been disabled. Will not process any pending requests."); + + return false; + } + + // Guard: Check whether one or more requests are pending + if (this->pendingRequests->isEmpty()) + { + // Later when the host driver adds a request to the queue, + // it will invoke `enable()` so the processor workloop will call this function again. + // At that time, the queue will not be empty, so this event source will process the new request. + pinfo("The request queue is empty. The event source will wait for the next enable() call."); + + return false; + } + else + { + // The processor workloop will invoke this function again, + // so this event source can process the next pending request. + pinfo("The request queue is not empty. Will inform the work loop to invoke this function again."); + + return true; + } +} + +/// +/// Initialize with the given queue +/// +/// @param owner Owner of this instance of an event source; the first parameter of the action routine +/// @param action A non-null action that is invoked when a block request has been processed +/// @param queue A list of pending requests +/// @return `true` on success, `false` otherwise. +/// +bool IOSDBlockRequestEventSource::initWithQueue(OSObject* owner, Action action, IOSDBlockRequestQueue* queue) +{ + if (action == nullptr) + { + perr("The action routine cannot be NULL."); + + return false; + } + + if (!super::init(owner, reinterpret_cast(action))) + { + return false; + } + + this->pendingRequests = queue; + + this->pendingRequests->retain(); + + return true; +} + +/// +/// Release this event source +/// +void IOSDBlockRequestEventSource::free() +{ + OSSafeReleaseNULL(this->pendingRequests); + + super::free(); +} + +/// +/// Create a block request event source with the given queue +/// +/// @param owner Owner of this instance of an event source; the first parameter of the action routine +/// @param action A non-null action that is invoked when a block request has been processed +/// @return A non-null event source on success, `nullptr` otherwise. +/// +IOSDBlockRequestEventSource* IOSDBlockRequestEventSource::createWithQueue(OSObject* owner, Action action, IOSDBlockRequestQueue* queue) +{ + auto instance = OSTypeAlloc(IOSDBlockRequestEventSource); + + if (instance == nullptr) + { + return nullptr; + } + + if (!instance->initWithQueue(owner, action, queue)) + { + instance->release(); + + return nullptr; + } + + return instance; +} diff --git a/RealtekPCIeCardReader/IOSDBlockRequestEventSource.hpp b/RealtekPCIeCardReader/IOSDBlockRequestEventSource.hpp new file mode 100644 index 0000000..c84f16b --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBlockRequestEventSource.hpp @@ -0,0 +1,64 @@ +// +// IOSDBlockRequestEventSource.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/9/21. +// + +#ifndef IOSDBlockRequestEventSource_hpp +#define IOSDBlockRequestEventSource_hpp + +#include +#include "IOSDBlockRequestQueue.hpp" + +/// An event source to signal the workloop to process a pending block request +class IOSDBlockRequestEventSource: public IOEventSource +{ + /// Constructors & Destructors + OSDeclareDefaultStructors(IOSDBlockRequestEventSource); + + using super = IOEventSource; + +public: + /// The type of the action routine that is invoked when a request has been processed + using Action = void (*)(OSObject*, IOSDBlockRequest*); + +private: + /// A list of pending requests + IOSDBlockRequestQueue* pendingRequests; + + /// + /// Check whether a SD block request is pending and if so process the request on the workloop + /// + /// @return `true` if the work loop should invoke this function again. + /// i.e. One or more requests are pending after the workloop has processed the current one. + /// + bool checkForWork() override; + + /// + /// Initialize with the given queue + /// + /// @param owner Owner of this instance of an event source; the first parameter of the action routine + /// @param action A non-null action that is invoked when a block request has been processed + /// @param queue A list of pending requests + /// @return `true` on success, `false` otherwise. + /// + bool initWithQueue(OSObject* owner, Action action, IOSDBlockRequestQueue* queue); + + /// + /// Release this event source + /// + void free() override; + +public: + /// + /// Create a block request event source with the given queue + /// + /// @param owner Owner of this instance of an event source; the first parameter of the action routine + /// @param action A non-null action that is invoked when a block request has been processed + /// @return A non-null event source on success, `nullptr` otherwise. + /// + static IOSDBlockRequestEventSource* createWithQueue(OSObject* owner, Action action, IOSDBlockRequestQueue* queue); +}; + +#endif /* IOSDBlockRequestEventSource_hpp */ diff --git a/RealtekPCIeCardReader/IOSDBlockRequestQueue.cpp b/RealtekPCIeCardReader/IOSDBlockRequestQueue.cpp new file mode 100644 index 0000000..838bd77 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBlockRequestQueue.cpp @@ -0,0 +1,63 @@ +// +// IOSDBlockRequestQueue.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/9/21. +// + +#include "IOSDBlockRequestQueue.hpp" +#include + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(IOSDBlockRequestQueue, IOCommandPool); + +/// +/// Create an empty queue protected by the given work loop +/// +/// @param workLoop The workloop that the queue should synchronize with +/// @return A non-null queue on success, `nullptr` otherwise. +/// +IOSDBlockRequestQueue* IOSDBlockRequestQueue::create(IOWorkLoop* workLoop) +{ + auto queue = OSTypeAlloc(IOSDBlockRequestQueue); + + if (queue == nullptr) + { + return nullptr; + } + + if (!queue->initWithWorkLoop(workLoop)) + { + queue->release(); + + return nullptr; + } + + return queue; +} + +/// +/// Check whether the queue is empty +/// +/// @return `true` if the queue is empty, `false` otherwise. +/// +bool IOSDBlockRequestQueue::isEmpty() +{ + bool empty; + + auto action = [](OSObject* queue, void* result, void*, void*, void*) -> IOReturn + { + auto instance = OSRequiredCast(IOSDBlockRequestQueue, queue); + + *reinterpret_cast(result) = queue_empty(&instance->fQueueHead); + + return kIOReturnSuccess; + }; + + this->fSerializer->runAction(action, &empty); + + return empty; +} diff --git a/RealtekPCIeCardReader/IOSDBlockRequestQueue.hpp b/RealtekPCIeCardReader/IOSDBlockRequestQueue.hpp new file mode 100644 index 0000000..a78df1e --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBlockRequestQueue.hpp @@ -0,0 +1,60 @@ +// +// IOSDBlockRequestQueue.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/9/21. +// + +#ifndef IOSDBlockRequestQueue_hpp +#define IOSDBlockRequestQueue_hpp + +#include +#include "IOSDBlockRequest.hpp" + +/// Represents a thread-safe queue of SD block requests +class IOSDBlockRequestQueue: public IOCommandPool +{ + /// Constructors & Destructors + OSDeclareDefaultStructors(IOSDBlockRequestQueue); + + using super = IOCommand; + +public: + /// + /// Create an empty queue protected by the given work loop + /// + /// @param workLoop The workloop that the queue should synchronize with + /// @return A non-null queue on success, `nullptr` otherwise. + /// + static IOSDBlockRequestQueue* create(IOWorkLoop* workLoop); + + /// + /// Check whether the queue is empty + /// + /// @return `true` if the queue is empty, `false` otherwise. + /// + bool isEmpty(); + + /// + /// Enqueue the given block request + /// + /// @param request A block request + /// + inline void enqueueRequest(IOSDBlockRequest* request) + { + this->returnCommand(request); + } + + /// + /// Dequeue a block request + /// + /// @return A non-null block request. + /// @warning The calling thread will not be blocked waiting for a request. + /// + inline IOSDBlockRequest* dequeueRequest() + { + return OSRequiredCast(IOSDBlockRequest, this->getCommand(false)); + } +}; + +#endif /* IOSDBlockRequestQueue_hpp */ diff --git a/RealtekPCIeCardReader/IOSDBlockStorageDevice.cpp b/RealtekPCIeCardReader/IOSDBlockStorageDevice.cpp new file mode 100644 index 0000000..d2ca4f5 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBlockStorageDevice.cpp @@ -0,0 +1,468 @@ +// +// IOSDBlockStorageDevice.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/2/21. +// + +#include "IOSDBlockStorageDevice.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(IOSDBlockStorageDevice, IOBlockStorageDevice); + +// +// MARK: - Card Characteristics +// + +/// +/// Fetch the card characteristics +/// +/// @return `true` on success, `false` otherwise. +/// +bool IOSDBlockStorageDevice::fetchCardCharacteristics() +{ + // 1. Block Size + if (this->driver->getCardBlockLength(this->blockSize) != kIOReturnSuccess) + { + perr("Failed to fetch the card block size."); + + return false; + } + + // 2. Block Number + if (this->driver->getCardNumBlocks(this->numBlocks) != kIOReturnSuccess) + { + perr("Failed to fetch the number of blocks on the card."); + + return false; + } + + // 3. Card Name + if (this->driver->getCardName(this->name, sizeof(this->name)) != kIOReturnSuccess) + { + perr("Failed to fetch the card name."); + + return false; + } + + // 4. Card Revision + if (this->driver->getCardRevision(this->revision, sizeof(this->revision)) != kIOReturnSuccess) + { + perr("Failed to fetch the card revision."); + + return false; + } + + // 5. Card Serial Number + if (this->driver->getCardSerialNumber(this->serialNumber) != kIOReturnSuccess) + { + perr("Failed to fetch the card serial number."); + + return false; + } + + snprintf(this->serialString, sizeof(this->serialString), "%08X", this->serialNumber); + + pinfo("Card Vendor = %s; Name = %s; Revision = %s; Serial Number = 0x%08X.", + this->driver->getCardVendor(), this->name, this->revision, this->serialNumber); + + pinfo("Card Block Size = %llu Bytes; NumBlocks = %llu; Capacity = %llu Bytes.", + this->blockSize, this->numBlocks, this->blockSize * this->numBlocks); + + return true; +} + +/// +/// Set the properties for the block storage device +/// +void IOSDBlockStorageDevice::setStorageProperties() +{ + OSDictionary* dictionary = OSDictionary::withCapacity(2); + + OSString* identifier = OSString::withCString("com.apple.iokit.IOSCSIArchitectureModelFamily"); + + OSString* iconFile = OSString::withCString("SD.icns"); + + if (dictionary != nullptr && identifier != nullptr && iconFile != nullptr) + { + psoftassert(dictionary->setObject("CFBundleIdentifier", identifier), "Failed to set the bundle identifier."); + + psoftassert(dictionary->setObject("IOBundleResourceFile", iconFile), "Failed to set the icon file name."); + + psoftassert(this->setProperty(kIOMediaIconKey, dictionary), "Failed to set the storage properties."); + } + + OSSafeReleaseNULL(dictionary); + + OSSafeReleaseNULL(identifier); + + OSSafeReleaseNULL(iconFile); +} + +// +// MARK: - Block Storage Protocol Implementations +// + +/// +/// Eject the SD card +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn IOSDBlockStorageDevice::doEjectMedia() +{ + pinfo("The storage subsystem requests to eject the media."); + + this->driver->onSDCardRemovedGated(); + + return kIOReturnSuccess; +} + +/// +/// Format the media to the given byte capacity +/// +/// @param byteCapacity The byte capacity to which the device is to be formatted +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @npte This function always returns `kIOReturnUnsupported` since our driver does not support it. +/// +IOReturn IOSDBlockStorageDevice::doFormatMedia(UInt64 byteCapacity) +{ + pinfo("The storage subsystem requests to format the media to the capacity %llu bytes.", byteCapacity); + + return kIOReturnUnsupported; +} + +/// +/// Get the allowable formatting byte capacities +/// +/// @param capacities A list of capacities +/// @param capacitiesMaxCount The number of values that can be stored in `capacities` +/// @return The number of capacity values returned in `capacities`; +/// The total number of capacity values available if `capacities` is NULL. +/// @note This function always returns `kIOReturnUnsupported` since our driver does not support it. +/// +UInt32 IOSDBlockStorageDevice::doGetFormatCapacities(UInt64* capacities, UInt32 capacitiesMaxCount) const +{ + pinfo("The storage subsystem requests to fetch all supported capacities."); + + return kIOReturnUnsupported; +} + +/// +/// Get the vendor of the SD card +/// +/// @return The vendor name. +/// +char* IOSDBlockStorageDevice::getVendorString() +{ + pinfo("The storage subsystem requests the vendor of the media."); + + return const_cast(this->driver->getCardVendor()); +} + +/// +/// Get the name of the SD card +/// +/// @return The product name. +/// +char* IOSDBlockStorageDevice::getProductString() +{ + pinfo("The storage subsystem requests the name of the media."); + + return this->name; +} + +/// +/// Get the revision of the SD card +/// +/// @return The card revision value. +/// +char* IOSDBlockStorageDevice::getRevisionString() +{ + pinfo("The storage subsystem requests the revision of the media."); + + return this->revision; +} + +/// +/// Get additional information of the SD card +/// +/// @return The additional information. +/// +char* IOSDBlockStorageDevice::getAdditionalDeviceInfoString() +{ + pinfo("The storage subsystem requests the addition information of the media."); + + return this->serialString; +} + +/// +/// Report the block size of the SD card +/// +/// @param blockSize The block size in bytes on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The block size should always be 512 bytes. +/// +IOReturn IOSDBlockStorageDevice::reportBlockSize(UInt64* blockSize) +{ + pinfo("The storage subsystem requests the block size of the media."); + + return this->driver->getCardBlockLength(*blockSize); +} + +/// +/// Report whether the SD card is ejectable +/// +/// @param isEjectable Set to `true` if the card can be ejected, `false` otherwise. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The card is always ejectable. +/// +IOReturn IOSDBlockStorageDevice::reportEjectability(bool* isEjectable) +{ + pinfo("The storage subsystem asks whether the media is ejectable."); + + *isEjectable = true; + + return kIOReturnSuccess; +} + +/// +/// Report the index of the highest valid block of the SD card +/// +/// @param maxBlock The block index on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn IOSDBlockStorageDevice::reportMaxValidBlock(UInt64* maxBlock) +{ + pinfo("The storage subsystem requests the index of the maximum block on the media."); + + return this->driver->getCardMaxBlockIndex(*maxBlock); +} + +/// +/// Report the state of the SD card +/// +/// @param mediaPresent Set to `true` if the SD card is present, `false` otherwise +/// @param changedState Set to `true` if the state has changed since the last query +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn IOSDBlockStorageDevice::reportMediaState(bool* mediaPresent, bool* changedState) +{ + pinfo("The storage subsystem requests the state of the media."); + + if (changedState != nullptr) + { + *changedState = false; + } + + return this->driver->isCardPresent(*mediaPresent); +} + +/// +/// Report whether the SD card is removable +/// +/// @param isRemovable Set to `true` if the card can be removed, `false` otherwise. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The card is always removable. +/// +IOReturn IOSDBlockStorageDevice::reportRemovability(bool* isRemovable) +{ + pinfo("The storage subsystem asks whether the media is removable."); + + *isRemovable = true; + + return kIOReturnSuccess; +} + +/// +/// Report whether the SD card is write protected +/// +/// @param isWriteProtected Set to `true` if the card is write protected, `false` otherwise +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn IOSDBlockStorageDevice::reportWriteProtection(bool* isWriteProtected) +{ + pinfo("The storage subsystem asks whether the media is write protected."); + + return this->driver->isCardWriteProtected(*isWriteProtected); +} + +/// +/// Submit an asynchronous I/O request +/// +/// @param buffer The data transfer buffer +/// @param block The starting block number +/// @param nblks The number of blocks to transfer +/// @param attributes Attributes of the data transfer +/// @param completion The completion routine to call once the data transfer completes +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The data direction is contained in the given memory descriptor. +/// @warning The given `completion` is allocated on the caller stack and thus is non-null. +/// However, since this function is asynchronous, the host driver must copy the completion to somewhere else, +/// otherwise a page fault will occur when the host driver invokes `IOStorage::complete()` on the completion object. +/// @seealso `IOBlockStorageDriver::breakUpRequestExecute()` and `IOBlockStorageDriver::executeRequest()`. +/// @ref https://opensource.apple.com/source/IOStorageFamily/IOStorageFamily-260.100.1/IOBlockStorageDriver.cpp.auto.html +/// +IOReturn IOSDBlockStorageDevice::doAsyncReadWrite(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblks, IOStorageAttributes* attributes, IOStorageCompletion* completion) +{ + // Request to access the SD card + pinfo("The storage subsystem requests to do asynchronous I/O transfer."); + + // Guard: Reject the request if the block device has been terminated + if (this->isInactive()) + { + perr("The block storage device has been terminated."); + + return kIOReturnNotAttached; + } + + // Guard: Check the buffer direction + IODirection direction = buffer->getDirection(); + + if (isNotOneOf(direction, kIODirectionIn, kIODirectionOut)) + { + perr("The buffer direction %d is invalid.", direction); + + return kIOReturnBadArgument; + } + + // Guard: Ensure that the request does not access an out-of-bounds block + if (block + nblks > this->numBlocks) + { + perr("The incoming request attempts to access an out-of-bounds block."); + + return kIOReturnBadArgument; + } + + // Examine and submit the request + if (direction == kIODirectionIn) + { + // This is a read request + if (nblks == 1) + { + // Request to read a single block + pinfo("The storage subsystem requests to read a single block from the block at %llu.", block); + + return this->driver->submitReadBlockRequest(buffer, block, nblks, attributes, completion); + } + else + { + // Request to read multiple blocks + pinfo("The storage subsystem requests to read %llu blocks from the block at %llu.", nblks, block); + + return this->driver->submitReadBlocksRequest(buffer, block, nblks, attributes, completion); + } + } + else + { + // This is a write request + if (nblks == 1) + { + // Request to write a single block + pinfo("The storage subsystem requests to write a single block to the block at %llu.", block); + + return this->driver->submitWriteBlockRequest(buffer, block, nblks, attributes, completion); + } + else + { + // Request to write multiple blocks + pinfo("The storage subsystem requests to write %llu blocks to the block at %llu.", nblks, block); + + return this->driver->submitWriteBlocksRequest(buffer, block, nblks, attributes, completion); + } + } +} + +// +// MARK: - IOService Implementations +// + +/// +/// Initialize the block storage device +/// +/// @param dictionary The matching dictionary +/// @return `true` on success, `false` otherwise. +/// +bool IOSDBlockStorageDevice::init(OSDictionary* dictionary) +{ + if (!super::init(dictionary)) + { + return false; + } + + this->setStorageProperties(); + + return true; +} + +/// +/// Start the block storage device +/// +/// @param provider An instance of the host driver +/// @return `true` on success, `false` otherwise. +/// +bool IOSDBlockStorageDevice::start(IOService* provider) +{ + pinfo("======================================="); + pinfo("Starting the SD block storage device..."); + pinfo("======================================="); + + if (!super::start(provider)) + { + perr("Failed to start the super class."); + + return false; + } + + // Fetch the host driver + this->driver = OSDynamicCast(IOSDHostDriver, provider); + + if (this->driver == nullptr) + { + perr("The provider is not a valid host driver instance."); + + return false; + } + + this->driver->retain(); + + // Fetch the card characteristics + if (!this->fetchCardCharacteristics()) + { + perr("Failed to fetch the card characteristics."); + + goto error; + } + + // Publish the service to start the storage subsystem + this->registerService(); + + pinfo("===================================="); + pinfo("The SD block storage device started."); + pinfo("===================================="); + + return true; + +error: + OSSafeReleaseNULL(this->driver); + + pinfo("============================================"); + perr("Failed to start the SD block storage device."); + pinfo("============================================"); + + return false; +} + +/// +/// Stop the block storage device +/// +/// @param provider An instance of the host driver +/// +void IOSDBlockStorageDevice::stop(IOService* provider) +{ + OSSafeReleaseNULL(this->driver); + + super::stop(provider); +} diff --git a/RealtekPCIeCardReader/IOSDBlockStorageDevice.hpp b/RealtekPCIeCardReader/IOSDBlockStorageDevice.hpp new file mode 100644 index 0000000..12057f6 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBlockStorageDevice.hpp @@ -0,0 +1,224 @@ +// +// IOSDBlockStorageDevice.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/2/21. +// + +#ifndef IOSDBlockStorageDevice_hpp +#define IOSDBlockStorageDevice_hpp + +#include +#include "IOSDHostDriver.hpp" + +/// Represents a generic SD block storage device +class IOSDBlockStorageDevice: public IOBlockStorageDevice +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareDefaultStructors(IOSDBlockStorageDevice); + + using super = IOBlockStorageDevice; + + // + // MARK: - Private Properties + // + + /// The host driver (provider) + IOSDHostDriver* driver; + + /// The block size in bytes + UInt64 blockSize; + + /// The number of blocks available + UInt64 numBlocks; + + /// A buffer to store the card name + char name[8]; + + /// A buffer to store the card revision string + char revision[8]; + + /// A buffer to store the serial number string + char serialString[16]; + + /// The card serial number + UInt32 serialNumber; + + // + // MARK: - Card Characteristics + // + + /// + /// Fetch the card characteristics + /// + /// @return `true` on success, `false` otherwise. + /// + bool fetchCardCharacteristics(); + + /// + /// Set the properties for the block storage device + /// + void setStorageProperties(); + +public: + // + // MARK: - Block Storage Protocol Implementations + // + + /// + /// Eject the SD card + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn doEjectMedia() override; + + /// + /// Format the media to the given byte capacity + /// + /// @param byteCapacity The byte capacity to which the device is to be formatted + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @npte This function always returns `kIOReturnUnsupported` since our driver does not support it. + /// + IOReturn doFormatMedia(UInt64 byteCapacity) override; + + /// + /// Get the allowable formatting byte capacities + /// + /// @param capacities A list of capacities + /// @param capacitiesMaxCount The number of values that can be stored in `capacities` + /// @return The number of capacity values returned in `capacities`; + /// The total number of capacity values available if `capacities` is NULL. + /// @note This function always returns `kIOReturnUnsupported` since our driver does not support it. + /// + UInt32 doGetFormatCapacities(UInt64* capacities, UInt32 capacitiesMaxCount) const override; + + /// + /// Get the vendor of the SD card + /// + /// @return The vendor name. + /// + char* getVendorString() override; + + /// + /// Get the name of the SD card + /// + /// @return The product name. + /// + char* getProductString() override; + + /// + /// Get the revision of the SD card + /// + /// @return The card revision value. + /// + char* getRevisionString() override; + + /// + /// Get additional information of the SD card + /// + /// @return The additional information. + /// + char* getAdditionalDeviceInfoString() override; + + /// + /// Report the block size of the SD card + /// + /// @param blockSize The block size in bytes on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The block size should always be 512 bytes. + /// + IOReturn reportBlockSize(UInt64* blockSize) override; + + /// + /// Report whether the SD card is ejectable + /// + /// @param isEjectable Set to `true` if the card can be ejected, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The card is always ejectable. + /// + IOReturn reportEjectability(bool* isEjectable) override; + + /// + /// Report the index of the highest valid block of the SD card + /// + /// @param maxBlock The block index on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn reportMaxValidBlock(UInt64* maxBlock) override; + + /// + /// Report the state of the SD card + /// + /// @param mediaPresent Set to `true` if the SD card is present, `false` otherwise + /// @param changedState Set to `true` if the state has changed since the last query + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn reportMediaState(bool* mediaPresent, bool* changedState = 0) override; + + /// + /// Report whether the SD card is removable + /// + /// @param isRemovable Set to `true` if the card can be removed, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The card is always removable. + /// + IOReturn reportRemovability(bool* isRemovable) override; + + /// + /// Report whether the SD card is write protected + /// + /// @param isWriteProtected Set to `true` if the card is write protected, `false` otherwise + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn reportWriteProtection(bool* isWriteProtected) override; + + /// + /// Submit an asynchronous I/O request + /// + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The data direction is contained in the given memory descriptor. + /// @warning The given `completion` is allocated on the caller stack and thus is non-null. + /// However, since this function is asynchronous, the host driver must copy the completion to somewhere else, + /// otherwise a page fault will occur when the host driver invokes `IOStorage::complete()` on the completion object. + /// @seealso `IOBlockStorageDriver::breakUpRequestExecute()` and `IOBlockStorageDriver::executeRequest()`. + /// @ref https://opensource.apple.com/source/IOStorageFamily/IOStorageFamily-260.100.1/IOBlockStorageDriver.cpp.auto.html + /// + IOReturn doAsyncReadWrite(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblks, IOStorageAttributes* attributes, IOStorageCompletion* completion) override; + + // + // MARK: - IOService Implementations + // + + /// + /// Initialize the block storage device + /// + /// @param dictionary The matching dictionary + /// @return `true` on success, `false` otherwise. + /// + bool init(OSDictionary* dictionary = nullptr) override; + + /// + /// Start the block storage device + /// + /// @param provider An instance of the host driver + /// @return `true` on success, `false` otherwise. + /// + bool start(IOService* provider) override; + + /// + /// Stop the block storage device + /// + /// @param provider An instance of the host driver + /// + void stop(IOService* provider) override; +}; + +#endif /* IOSDBlockStorageDevice_hpp */ diff --git a/RealtekPCIeCardReader/IOSDBusConfig.hpp b/RealtekPCIeCardReader/IOSDBusConfig.hpp new file mode 100644 index 0000000..04234bc --- /dev/null +++ b/RealtekPCIeCardReader/IOSDBusConfig.hpp @@ -0,0 +1,124 @@ +// +// IOSDBusConfig.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/30/21. +// + +#ifndef IOSDBusConfig_hpp +#define IOSDBusConfig_hpp + +#include + +#define MMC_VDD_165_195 0x00000080 /* VDD voltage 1.65 - 1.95 */ +#define MMC_VDD_20_21 0x00000100 /* VDD voltage 2.0 ~ 2.1 */ +#define MMC_VDD_21_22 0x00000200 /* VDD voltage 2.1 ~ 2.2 */ +#define MMC_VDD_22_23 0x00000400 /* VDD voltage 2.2 ~ 2.3 */ +#define MMC_VDD_23_24 0x00000800 /* VDD voltage 2.3 ~ 2.4 */ +#define MMC_VDD_24_25 0x00001000 /* VDD voltage 2.4 ~ 2.5 */ +#define MMC_VDD_25_26 0x00002000 /* VDD voltage 2.5 ~ 2.6 */ +#define MMC_VDD_26_27 0x00004000 /* VDD voltage 2.6 ~ 2.7 */ +#define MMC_VDD_27_28 0x00008000 /* VDD voltage 2.7 ~ 2.8 */ +#define MMC_VDD_28_29 0x00010000 /* VDD voltage 2.8 ~ 2.9 */ +#define MMC_VDD_29_30 0x00020000 /* VDD voltage 2.9 ~ 3.0 */ +#define MMC_VDD_30_31 0x00040000 /* VDD voltage 3.0 ~ 3.1 */ +#define MMC_VDD_31_32 0x00080000 /* VDD voltage 3.1 ~ 3.2 */ +#define MMC_VDD_32_33 0x00100000 /* VDD voltage 3.2 ~ 3.3 */ +#define MMC_VDD_33_34 0x00200000 /* VDD voltage 3.3 ~ 3.4 */ +#define MMC_VDD_34_35 0x00400000 /* VDD voltage 3.4 ~ 3.5 */ +#define MMC_VDD_35_36 0x00800000 /* VDD voltage 3.5 ~ 3.6 */ + +/// +/// Reflects the struct `mmc_ios` and contains I/O related bus settings +/// +/// @reference `mmc_ios_show()` defined in `drivers/mmc/core/debugfs.c`. +/// +struct IOSDBusConfig +{ + /// Clock rate in Hz + UInt32 clock; + + /// The bit index of the selected voltage range + UInt32 vdd; + + /// The amount of time in milliseconds to wait until the power is stable + UInt32 powerDelay; + + /// Command output mode + /// Ingored by the Realtek host driver + enum class BusMode: UInt8 + { + kOpenDrain = 1, + kPushPull = 2, + } busMode; + + /// SPI chip select mode + /// Ignored by the Realtek host driver: + /// SPI is always high, preventing the chip from entering into the SPI mode + enum class ChipSelect: UInt8 + { + kDoNotCare = 0, + kHigh = 1, + kLow = 2, + } chipSelect; + + /// Power supply mode + enum class PowerMode: UInt8 + { + kPowerOff = 0, + kPowerUp = 1, + kPowerOn = 2, + kPowerUndefined = 3, + } powerMode; + + /// Data bus width + enum class BusWidth: UInt8 + { + k1Bit = 0, + k4Bit = 2, + k8Bit = 3, + } busWidth; + + /// Bus Speed Mode (Timing) + enum class BusTiming: UInt8 + { + kLegacy = 0, // SD Default Speed ( 12.5MB/s, 25MHz, 3.3V) + kMMCHighSpeed = 1, + kSDHighSpeed = 2, // SD High Speed ( 25.0MB/s, 50MHz, 3.3V) + kUHSSDR12 = 3, // SD UHS-I SDR12 ( 12.5MB/s, 25MHz, 1.8V) + kUHSSDR25 = 4, // SD UHS-I SDR25 ( 25.0MB/s, 50MHz, 1.8V) + kUHSSDR50 = 5, // SD UHS-I SDR50 ( 50.0MB/s, 100MHz, 1.8V) + kUHSSDR104 = 6, // SD UHS-I SDR104 (104.0MB/s, 208MHz, 1.8V) + kUHSDDR50 = 7, // SD UHS-I DDR50 ( 50.0MB/s, 50MHz, 1.8V) + kMMCDDR52 = 8, + kMMCHS200 = 9, + kMMCHS400 = 10, + kSDExpress = 11, + kSDExpress1d2V = 12 + } busTiming; + + /// Signalling voltage + enum class SignalVoltage: UInt8 + { + k3d3V = 0, // SDSC/HC Card + k1d8V = 1, // SDHC/XC Card + k1d2V = 2, // SD Express Card + } signalVoltage; + + /// Driver Type + /// Not used by the Realtek host driver + enum class DriverType: UInt8 + { + kTypeB = 0, + kTypeA = 1, + kTypeC = 2, + kTypeD = 3, + } driverType; + + void print() const + { + // TODO: IMP THIS + } +}; + +#endif /* IOSDBusConfig_hpp */ diff --git a/RealtekPCIeCardReader/IOSDCard.cpp b/RealtekPCIeCardReader/IOSDCard.cpp new file mode 100644 index 0000000..87ea8aa --- /dev/null +++ b/RealtekPCIeCardReader/IOSDCard.cpp @@ -0,0 +1,987 @@ +// +// IOSDCard.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/3/21. +// + +#include "IOSDCard.hpp" +#include "IOSDHostDriver.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(IOSDCard, IOService); + +/// +/// Initialize the card with the given OCR register value +/// +/// @param driver The host driver +/// @param ocr The current operating condition register value +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `mmc_sd_init_card()` defined in `sd.c`. +/// +bool IOSDCard::init(IOSDHostDriver* driver, UInt32 ocr) +{ + if (!super::init()) + { + return false; + } + + // =============================== + // | BEGIN PORTED mmc_sd_get_cid | + // =============================== + // The initialization routine may change the OCR value + UInt32 pocr = ocr; + + this->driver = driver; + + pinfo("Initializing the SD card with OCR = 0x%08x.", ocr); + + // Tell the card to go to the idle state + if (this->driver->CMD0() != kIOReturnSuccess) + { + perr("Failed to tell the card to go back to the idle state."); + + return false; + } + + pinfo("The card is now in the idle state."); + + // Check whether the card supports SD 2.0 + // TODO: Add a CMD8 variant that takes the OCR as its argument + if (this->driver->CMD8((ocr & 0xFF8000) != 0) == kIOReturnSuccess) + { + pinfo("Found a SD 2.0 compliant card."); + + ocr |= OCR::kCardCapacityStatus; + } + + // Check whether the host supports UHS-I mode + // If so, we assume that the card also supports UHS-I mode + // and request the card to switch to 1.8V signal voltage + // Later the card will return a OCR value indicating whether it supports UHS-I mode + if (this->driver->hostSupportsUltraHighSpeedMode()) + { + pinfo("The host supports UHS-I mode and will try to request the card to switch to 1.8V signal voltage."); + + ocr |= OCR::kRequest1d8V; + } + + // Check whether the host can supply more than 150mA at the current voltage level + // If so, we must follow the specification to set the XPC bit + if (this->driver->getHostMaxCurrent() > 150) + { + pinfo("The host can supply more than 150mA."); + + ocr |= OCR::kSDXCPowerControl; + } + + // Send the new OCR value + UInt32 rocr; + + pinfo("Sending the new OCR value 0x%08x...", ocr); + + if (this->driver->ACMD41(ocr, rocr) != kIOReturnSuccess) + { + perr("Failed to send the new OCR value."); + + return false; + } + + pinfo("OCR value returned from the card is 0x%08x.", rocr); + + // Check whether the card accepts the 1.8V signal voltage + if (BitOptions(rocr).contains(OCR::kCardCapacityStatus | OCR::kAccepted1d8V)) + { + pinfo("The card has accepted the 1.8V signal voltage."); + + // TODO: Retry on failure not implemented; Just fails? + // Tell the card and the host to switch to 1.8V + if (!this->enableUltraHighSpeedSignalVoltage(pocr)) + { + perr("Failed to switch the host and the card to 1.8V."); + + return false; + } + + pinfo("Both the host and the card has switched to the 1.8V signal voltage."); + } + + // Fetch and parse the card identification data + pinfo("Fetching the card identification data..."); + + if (this->driver->CMD2(this->cid) != kIOReturnSuccess) + { + perr("Failed to fetch the card identification data."); + + return false; + } + + pinfo("The card identification data has been fetched and decoded."); + + // =============================== + // | END PORTED mmc_sd_get_cid | + // =============================== + // ================================= + // | BEGIN PORTED mmc_sd_init_card | + // ================================= + + // Ask the card to publish its relative address + pinfo("Requesting the card relative address..."); + + if (this->driver->CMD3(this->rca) != kIOReturnSuccess) + { + perr("Failed to fetch the card relative address."); + + return false; + } + + pinfo("The card relative address is 0x%08x.", this->rca); + + // Fetch the card specific data + pinfo("Fetching the card specific data..."); + + if (this->driver->CMD9(this->rca, this->csd) != kIOReturnSuccess) + { + perr("Failed to fetch the card specific data."); + + return false; + } + + pinfo("The card specific data has been fetched and decoded."); + + // Select the card: Will enter into the transfer state + pinfo("Asking the card to enter into the transfer state..."); + + if (this->driver->CMD7(this->rca) != kIOReturnSuccess) + { + perr("Failed to select the card."); + + return false; + } + + pinfo("The card is now in the transfer state."); + + // TODO: Setup Card (If not reinit) + // Fetch and parse the SD configuration data from the card + pinfo("Fetching the card configuration data..."); + + if (this->driver->ACMD51(this->rca, this->scr) != kIOReturnSuccess) + { + perr("Failed to fetch the SD configuration data."); + + return false; + } + + pinfo("The card configuration data has been fetched and decoded."); + + // Fetch the SD status register value from the card + pinfo("Fetching the SD status register value..."); + + if (this->driver->ACMD13(this->rca, this->ssr) != kIOReturnSuccess) + { + perr("Failed to fetch the SD status data."); + + return false; + } + + pinfo("The SD status register value has been fetched."); + + // TODO: INIT ERASE??? How does macOS handle erase + + // Fetch the switch information from the card + pinfo("Fetching the switch capabilities from the card..."); + + if (!this->readSwitchCapabilities()) + { + perr("Failed to fetch the switch information from the card."); + + return false; + } + + pinfo("The switch capabilities have been fetched."); + + // TODO: CHECK ALREADY 1.8V + /* + * If the card has not been power cycled, it may still be using 1.8V + * signaling. Detect that situation and try to initialize a UHS-I (1.8V) + * transfer mode. + */ + + BitOptions currentBusMode = this->switchCaps.sd3BusMode; + + if (currentBusMode.contains(SwitchCaps::BusMode::kModeUHSSDR50) || + currentBusMode.contains(SwitchCaps::BusMode::kModeUHSSDR104) || + currentBusMode.contains(SwitchCaps::BusMode::kModeUHSDDR50)) + { + if (this->driver->getHostDevice()->getHostBusConfig().signalVoltage != IOSDBusConfig::SignalVoltage::k1d8V) + { + pinfo("The card is already operating at 1.8V but the host does not."); + + pinfo("Abort the initialization. Not implemented."); + + return false; + } + } + + + // TODO: Consider fallback mechanism + + // Guard: Check whether the card supports the high speed mode + if (this->scr.spec < SCR::Spec::kVersion1d1x) + { + pinfo("The card does not support SD 1.10+. High Speed mode is not supported."); + + return this->initDefaultSpeedMode(); + } + + // Guard: Check whether the card supports switch functions + if (!BitOptions(this->csd.cardCommandClasses).contains(CSD::CommandClass::kSwitch)) + { + pinfo("The card does not support switch functions. High Speed mode is not supported."); + + return this->initDefaultSpeedMode(); + } + + // Guard: Check whether the host supports the high speed mode + if (!this->driver->hostSupportsHighSpeedMode()) + { + pinfo("The host does not support the high speed mode."); + + return this->initDefaultSpeedMode(); + } + + // Guard: Check the card switch functionality + if (this->switchCaps.maxClockFrequencies.highSpeedMode == 0) + { + pinfo("The maximum clock frequency value for the high speed mode is zero."); + + return this->initDefaultSpeedMode(); + } + + // Guard: Check whether the card supports the UHS-I mode + if (BitOptions(rocr).contains(OCR::kAccepted1d8V) && + this->driver->hostSupportsUltraHighSpeedMode()) + { + pinfo("Both the host and the card support the ultra high speed mode."); + + return this->initUltraHighSpeedMode(); + } + + // Guard: Tell the card to switch to the high speed mode + pinfo("Asking the card to switch to the high speed mode..."); + + UInt8 status[64] = {}; + + if (this->driver->CMD6(1, 0, SwitchCaps::BusSpeed::kSpeedHighSpeed, status) != kIOReturnSuccess) + { + perr("Failed to issue the CMD6 while the card supports switch functions."); + + return false; + } + + // Guard: Check the card status + if ((status[16] & 0xF) == SwitchCaps::BusSpeed::kSpeedHighSpeed) + { + pinfo("The card has switched to the high speed mode."); + + return this->initHighSpeedMode();; + } + else + { + perr("The card fails to switch to the high speed mode. Will use the default speed mode."); + + return this->initDefaultSpeedMode(); + } +} + +/// +/// [Helper] Initialize the card with Default Speed Mode enabled +/// +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces the default speed portion of `mmc_sd_init_card()` defined in `sd.c`. +/// +bool IOSDCard::initDefaultSpeedMode() +{ + // Finish the card initialization sequence with the default speed enabled + pinfo("Initializing the card at the default speed mode..."); + + // Guard: Set the bus speed + UInt32 clock = min(UINT32_MAX, this->csd.maxDataTransferRate); + + if (this->driver->setBusClock(clock) != kIOReturnSuccess) + { + perr("Failed to set the bus clock to %u Hz.", clock); + + return false; + } + + // Switch to wider bus if possible + // Guard: Check whether the card supports the 4-bit bus + if (!BitOptions(this->scr.busWidths).contains(SCR::BusWidth::k4Bit)) + { + pinfo("The card does not support the 4-bit bus."); + + return true; + } + + // Guard: Check whether the host supports the 4-bit bus + if (!this->driver->hostSupports4BitBusWidth()) + { + pinfo("The host does not support the 4-bit bus."); + + return true; + } + + // Enable the 4-bit wide bus + if (!this->enable4BitWideBus()) + { + perr("Failed to enable the 4-bit wide bus."); + + return false; + } + + pinfo("The 4-bit bus has been enabled.") + + pinfo("The card has been initialized with the default speed mode enabled."); + + return true; +} + +/// +/// [Helper] Initialize the card with High Speed Mode enabled +/// +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces the high speed portion of `mmc_sd_init_card()` defined in `sd.c`. +/// +bool IOSDCard::initHighSpeedMode() +{ + // Finish the card initialization sequence with the high speed enabled + pinfo("Initializing the card at the high speed mode..."); + + // Guard: Set the bus timing + pinfo("Setting the bus timing to the high speed mode..."); + + if (this->driver->setBusTiming(IOSDBusConfig::BusTiming::kSDHighSpeed) != kIOReturnSuccess) + { + perr("Failed to set the bus timing to High Speed."); + + return false; + } + + pinfo("The bus timing has been set to the high speed mode."); + + // Guard: Set the bus speed + UInt32 clock = this->switchCaps.maxClockFrequencies.highSpeedMode; + + pinfo("Setting the bus clock to %u Hz.", clock); + + if (this->driver->setBusClock(clock) != kIOReturnSuccess) + { + perr("Failed to set the bus clock to %u Hz.", clock); + + return false; + } + + pinfo("The bus clock has been set to %u Hz.", clock); + + // Switch to wider bus if possible + // Guard: Check whether the card supports the 4-bit bus + if (!BitOptions(this->scr.busWidths).contains(SCR::BusWidth::k4Bit)) + { + pinfo("The card does not support the 4-bit bus."); + + return true; + } + + // Guard: Check whether the host supports the 4-bit bus + if (!this->driver->hostSupports4BitBusWidth()) + { + pinfo("The host does not support the 4-bit bus."); + + return true; + } + + // Enable the 4-bit wide bus + pinfo("Enabling the 4-bit bus..."); + + if (!this->enable4BitWideBus()) + { + perr("Failed to enable the 4-bit wide bus."); + + return false; + } + + pinfo("The 4-bit bus has been enabled.") + + // The card initialization sequence finishes + pinfo("The card has been initialized with the high speed mode enabled."); + + return true; +} + +/// +/// [Helper] Initialize the card with UHS-I Mode enabled +/// +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `mmc_sd_init_uhs_card()` defined in `sd.c`. +/// +bool IOSDCard::initUltraHighSpeedMode() +{ + // Finish the card initialization sequence with the ultra high speed enabled + pinfo("Initializing the card at the ultra high speed mode..."); + + // Enable the 4-bit wide bus + pinfo("Enabling the 4-bit bus..."); + + if (!this->enable4BitWideBus()) + { + perr("Failed to enable the 4-bit wide bus required by the UHS-I mode."); + + return false; + } + + pinfo("The 4-bit bus has been enabled.") + + // Select the bus speed mode based on the host and card capability + SwitchCaps::BusSpeed speedMode = this->selectUltraHighSpeedBusSpeed(); + + pinfo("Selected bus speed mode is %d.", speedMode); + + // Set the driver strength for the card + pinfo("Setting the driver strength for the card..."); + + if (!this->setUHSDriverType(speedMode)) + { + perr("Failed to set the driver type for the card."); + + return false; + } + + pinfo("The driver strength has been set for the card."); + + // Set the current limit for the card + pinfo("Setting the current limit for the card..."); + + if (!this->setUHSCurrentLimit(speedMode)) + { + perr("Failed to set the current limit for the card."); + + return false; + } + + pinfo("The current limit has been set for the card."); + + // Set the bus speed mode of the card + pinfo("Setting the bus speed mode for the card..."); + + if (!this->setUHSBusSpeedMode(speedMode)) + { + perr("Failed to set the bus speed mode of the card."); + + return false; + } + + pinfo("The bus speed mode has been set for the card."); + + // Check whether the host needs tuning + if (speedMode < SwitchCaps::BusSpeed::kSpeedUHSSDR50) + { + pinfo("The current bus speed mode does not require tuning."); + + return true; + } + + // Execute the tuning + pinfo("The current bus speed mode requires tuning."); + + if (this->driver->executeTuning() != kIOReturnSuccess) + { + perr("Failed to execute tuning for the current bus speed mode %d.", speedMode); + + return speedMode == SwitchCaps::BusSpeed::kSpeedUHSDDR50; + } + + pinfo("Tuning has finished."); + + // The card initialization sequence finishes + pinfo("The card has been initialized with the ultra high speed mode enabled."); + + return true; +} + +/// +/// [Helper] Read the card switch capabilities +/// +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `mmc_read_switch()` defined in `sd.c`. +/// +bool IOSDCard::readSwitchCapabilities() +{ + // Check the SD specification supported by the card + if (this->scr.spec < SCR::Spec::kVersion1d1x) + { + pinfo("The card does not support SD 1.10+. Will skip the switch capabilities."); + + return true; + } + + // Check whether the card supports switch functions + if (!BitOptions(this->csd.cardCommandClasses).contains(CSD::CommandClass::kSwitch)) + { + pinfo("The card conforms to SD 1.10+ but does not support mandatory switch functions."); + + return false; + } + + // Fetch the switch function status + UInt8 status[64] = {}; + + if (this->driver->CMD6(0, 0, 0, status) != kIOReturnSuccess) + { + perr("Failed to issue the CMD6."); + + return false; + } + + if (BitOptions(status[13]).contains(SwitchCaps::BusMode::kModeHighSpeed)) + { + this->switchCaps.maxClockFrequencies.highSpeedMode = SwitchCaps::MaxClockFrequency::kClockHighSpeed; + } + + if (this->scr.spec3 != 0) + { + this->switchCaps.sd3BusMode = status[13]; + + this->switchCaps.sd3DriverType = status[9]; + + this->switchCaps.sd3MaxCurrent = status[7] | status[6] << 8; + } + + return true; +} + +/// +/// [Helper] Enable the 4-bit wide bus for data transfer +/// +/// @return `true` on success, `false` otherwise. +/// +bool IOSDCard::enable4BitWideBus() +{ + // Switch to the 4-bit bus (card) + pinfo("Asking the card to use the 4-bit bus for data transfer."); + + if (this->driver->ACMD6(this->rca, IOSDBusConfig::BusWidth::k4Bit) != kIOReturnSuccess) + { + perr("Failed to issue the ACMD6 to switch to the 4-bit bus."); + + return false; + } + + pinfo("The card will use the 4-bit bus for data transfer."); + + // Switch to the 4-bit bus (host) + pinfo("Asking the host device to use the 4-bit bus for data transfer."); + + if (this->driver->setBusWidth(IOSDBusConfig::BusWidth::k4Bit) != kIOReturnSuccess) + { + perr("Failed to ask the host to switch to the 4-bit bus."); + + return false; + } + + pinfo("The host device will use the 4-bit bus for data transfer."); + + return true; +} + +/// +/// [Helper] Enable the signal voltage for the UHS-I mode +/// +/// @param ocr The current operating condition register value +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `mmc_set_uhs_voltage()` defined in `core.c`. +/// +bool IOSDCard::enableUltraHighSpeedSignalVoltage(UInt32 ocr) +{ + pinfo("Enabling the signal voltage for the UHS-I mode..."); + + // Tell the card to switch to 1.8V + if (this->driver->CMD11() != kIOReturnSuccess) + { + perr("Failed to tell the card to switch to 1.8V."); + + return false; + } + + // The card should put the CMD and DATA lines low + // immediately after sending the response of CMD11 + // Wait for 1ms and then check the line status + IOSleep(1); + + // Since the driver has issued the CMD11, + // if the driver fails to switch to 1.8V, + // it must restart the power of the card. + bool retVal = false; + + // Verify the line status + // TODO: DATA LOW == DATA IDLE??? +// bool status = false; +// +// if (this->driver->getHostDevice()->isCardDataLineBusy(status) != kIOReturnSuccess) +// { +// perr("Failed to retrieve the data line status."); +// +// goto cycle; +// } +// +// if (status) +// { +// perr("The card should put the CMD and DATA lines low at this moment."); +// +// goto cycle; +// } + + // Tell the host the switch to 1.8V + if (this->driver->setUltraHighSpeedSignalVoltage() != kIOReturnSuccess) + { + perr("Failed to tell the host to switch to 1.8V."); + + goto cycle; + } + + // Wait at least 1ms + IOSleep(1); + + retVal = true; + + // If failed to switch to 1.8V, the card data lines are low + // TODO: DATA LOW == DATA IDLE??? +// if (this->driver->getHostDevice()->isCardDataLineBusy(status) != kIOReturnSuccess) +// { +// perr("Failed to retrieve the data line status."); +// +// goto cycle; +// } +// +// if (status) +// { +// pinfo("The signal voltage has been enabled for the UHS-I mode."); +// +// retVal = true; +// } +// else +// { +// perr("Failed to enable the signal voltage for the UHS-I mode."); +// } + +cycle: + if (!retVal) + { + psoftassert(this->driver->powerCycle(ocr) == kIOReturnSuccess, + "Failed to restart the power of the card."); + } + + return retVal; +} + +/// +/// [Helper] Select the bus speed for the UHS-I mode +/// +/// @return The bus speed supported by both the card and the host. +/// @note Port: This function replaces `sd_update_bus_speed_mode()` defined in `sd.c`. +/// +SwitchCaps::BusSpeed IOSDCard::selectUltraHighSpeedBusSpeed() +{ + BitOptions hostCaps = this->driver->getHostDevice()->getCaps1(); + + BitOptions cardCaps = this->switchCaps.sd3BusMode; + + if (hostCaps.contains(MMC_CAP_UHS_SDR104) && cardCaps.contains(SwitchCaps::BusMode::kModeUHSSDR104)) + { + pinfo("Will use the speed mode UHS-I SDR104."); + + return SwitchCaps::BusSpeed::kSpeedUHSSDR104; + } + + if (hostCaps.contains(MMC_CAP_UHS_DDR50) && cardCaps.contains(SwitchCaps::BusMode::kModeUHSDDR50)) + { + pinfo("Will use the speed mode UHS-I DDR50."); + + return SwitchCaps::BusSpeed::kSpeedUHSDDR50; + } + + if (hostCaps.contains(MMC_CAP_UHS_SDR50) && cardCaps.contains(SwitchCaps::BusMode::kModeUHSSDR50)) + { + pinfo("Will use the speed mode UHS-I SDR50."); + + return SwitchCaps::BusSpeed::kSpeedUHSSDR50; + } + + if (hostCaps.contains(MMC_CAP_UHS_SDR25) && cardCaps.contains(SwitchCaps::BusMode::kModeUHSSDR25)) + { + pinfo("Will use the speed mode UHS-I SDR25."); + + return SwitchCaps::BusSpeed::kSpeedUHSSDR25; + } + + if (hostCaps.contains(MMC_CAP_UHS_SDR12) && cardCaps.contains(SwitchCaps::BusMode::kModeUHSSDR12)) + { + pinfo("Will use the speed mode UHS-I SDR12."); + + return SwitchCaps::BusSpeed::kSpeedUHSSDR12; + } + + pfatal("Should never reach at here. The host and/or card capabilities are invalid."); +} + +bool IOSDCard::setUHSDriverType(SwitchCaps::BusSpeed busSpeed) +{ + return true; +} + +/// +/// [Helper] Set the current limit for the UHS-I card +/// +/// @param busSpeed The bus speed +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `sd_set_current_limit()` defined in `sd.c`. +/// @note This function is a part of the UHS-I card initialization routine `initUltraHighSpeedMode()`. +/// +bool IOSDCard::setUHSCurrentLimit(SwitchCaps::BusSpeed busSpeed) +{ + // Current limit switch is only defined for SDR50, SDR104 and DDR50 modes + // For other bus speed modes, we do not change the current limit + if (isNotOneOf(busSpeed, SwitchCaps::BusSpeed::kSpeedUHSSDR50, SwitchCaps::BusSpeed::kSpeedUHSSDR104, SwitchCaps::BusSpeed::kSpeedUHSDDR50)) + { + pinfo("No need to change the current limit."); + + return true; + } + + // The host has different current capabilities when operating at different voltages + // Get the maximum current supported by the host device + UInt32 hostMaxCurrent = this->driver->getHostMaxCurrent(); + + UInt32 cardMaxCurrent = this->switchCaps.sd3MaxCurrent; + + // The new current limit + UInt8 limit; + + if (hostMaxCurrent >= 800 && BitOptions(cardMaxCurrent).contains(SwitchCaps::MaxCurrent::kMaxCurrent800mA)) + { + limit = SwitchCaps::SetCurrentLimit::kCurrentLimit800mA; + } + else if (hostMaxCurrent >= 600 && BitOptions(cardMaxCurrent).contains(SwitchCaps::MaxCurrent::kMaxCurrent600mA)) + { + limit = SwitchCaps::SetCurrentLimit::kCurrentLimit600mA; + } + else if (hostMaxCurrent >= 400 && BitOptions(cardMaxCurrent).contains(SwitchCaps::MaxCurrent::kMaxCurrent400mA)) + { + limit = SwitchCaps::SetCurrentLimit::kCurrentLimit400mA; + } + else if (hostMaxCurrent >= 200 && BitOptions(cardMaxCurrent).contains(SwitchCaps::MaxCurrent::kMaxCurrent200mA)) + { + limit = SwitchCaps::SetCurrentLimit::kCurrentLimit200mA; + } + else + { + return true; + } + + // Switch the current limit + UInt8 status[64] = {}; + + if (this->driver->CMD6(1, 3, limit, status) != kIOReturnSuccess) + { + perr("Failed to switch the current limit."); + + return false; + } + + // Verify the current limit + if (((status[15] >> 4) & 0x0F) != limit) + { + perr("The new current limit is not the requested one."); + + return false; + } + + return true; +} + +/// +/// [Helper] Set the bus speed for the UHS-I card +/// +/// @param busSpeed The bus speed +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `sd_set_bus_speed_mode()` defined in `sd.c`. +/// @note This function is a part of the UHS-I card initialization routine `initUltraHighSpeedMode()`. +/// +bool IOSDCard::setUHSBusSpeedMode(SwitchCaps::BusSpeed busSpeed) +{ + IOSDBusConfig::BusTiming timing; + + switch (busSpeed) + { + case SwitchCaps::BusSpeed::kSpeedUHSSDR104: + { + timing = IOSDBusConfig::BusTiming::kUHSSDR104; + + this->switchCaps.maxClockFrequencies.ultraHighSpeedMode = SwitchCaps::MaxClockFrequency::kClockUHSSDR104; + + break; + } + + case SwitchCaps::BusSpeed::kSpeedUHSDDR50: + { + timing = IOSDBusConfig::BusTiming::kUHSDDR50; + + this->switchCaps.maxClockFrequencies.ultraHighSpeedMode = SwitchCaps::MaxClockFrequency::kClockUHSDDR50; + + break; + } + + case SwitchCaps::BusSpeed::kSpeedUHSSDR50: + { + timing = IOSDBusConfig::BusTiming::kUHSSDR50; + + this->switchCaps.maxClockFrequencies.ultraHighSpeedMode = SwitchCaps::MaxClockFrequency::kClockUHSSDR50; + + break; + } + + case SwitchCaps::BusSpeed::kSpeedUHSSDR25: + { + timing = IOSDBusConfig::BusTiming::kUHSSDR25; + + this->switchCaps.maxClockFrequencies.ultraHighSpeedMode = SwitchCaps::MaxClockFrequency::kClockUHSSDR25; + + break; + } + + case SwitchCaps::BusSpeed::kSpeedUHSSDR12: + { + timing = IOSDBusConfig::BusTiming::kUHSSDR12; + + this->switchCaps.maxClockFrequencies.ultraHighSpeedMode = SwitchCaps::MaxClockFrequency::kClockUHSSDR12; + + break; + } + + default: + { + perr("Detected an invalid bus speed for UHS-I card."); + + return false; + } + } + + // Tell the card to switch the bus speed + UInt8 status[64] = {}; + + if (this->driver->CMD6(1, 0, busSpeed, status) != kIOReturnSuccess) + { + perr("Failed to issue the CMD6 to switch 1the bus speed."); + + return false; + } + + // Verify the new bus speed + if ((status[16] & 0x0F) != busSpeed) + { + perr("The new bus speed is not the one requested."); + + return false; + } + + // Set the host-side timing + if (this->driver->setBusTiming(timing) != kIOReturnSuccess) + { + perr("Failed to set the host-side timing."); + + return false; + } + + // Set the host-side clock + if (this->driver->setBusClock(this->switchCaps.maxClockFrequencies.ultraHighSpeedMode) != kIOReturnSuccess) + { + perr("Failed to set the host-side clock."); + + return false; + } + + return true; +} + +/// +/// Get the card characteristics +/// +/// @return A dictionary that contains card characteristics which can be recognized by the System Profiler. +/// @note The caller is responsible for releasing the returned dictionary. +/// +OSDictionaryPtr IOSDCard::getCardCharacteristics() +{ + OSDictionary* dictionary = OSDictionary::withCapacity(8); + + char name[8] = {}; + + char revision[8] = {}; + + char date[8] = {}; + + passert(this->getCardName(name, sizeof(name)), "Should be able to get the card name."); + + passert(this->getCardRevision(revision, sizeof(revision)), "Should be able to get the card revision."); + + passert(this->getCardProductionDate(date, sizeof(date)), "Should be able to get the card production date."); + + if (dictionary != nullptr && + OSDictionaryAddStringToDictionary(dictionary, "Card Type", this->getCardType()) && + OSDictionaryAddStringToDictionary(dictionary, "Specification Version", this->getSpecificationVersion()) && + OSDictionaryAddStringToDictionary(dictionary, "Product Name", name) && + OSDictionaryAddStringToDictionary(dictionary, "Product Revision Level", revision) && + OSDictionaryAddStringToDictionary(dictionary, "Manufacturing Date", date) && + OSDictionaryAddDataToDictionary(dictionary, "Serial Number", &this->cid.serial, sizeof(this->cid.serial)) && + OSDictionaryAddDataToDictionary(dictionary, "Manufacturer ID", &this->cid.manufacturer, sizeof(this->cid.manufacturer)) && + OSDictionaryAddDataToDictionary(dictionary, "Application ID", &this->cid.oem, sizeof(this->cid.oem))) + { + return dictionary; + } + + OSSafeReleaseNULL(dictionary); + + return nullptr; +} + +/// +/// Create the card and initialize it with the given OCR value +/// +/// @param driver The non-null host driver +/// @param ocr The OCR value that contains the voltage level supported by the host and the card +/// @return A non-null card instance on success, `nullptr` otherwise. +/// +IOSDCard* IOSDCard::createWithOCR(IOSDHostDriver* driver, UInt32 ocr) +{ + auto card = OSTypeAlloc(IOSDCard); + + if (card == nullptr) + { + perr("Failed to allocate a card instance.") + + return nullptr; + } + + if (!card->init(driver, ocr)) + { + perr("Failed to initialize the card."); + + card->release(); + + return nullptr; + } + + return card; +} diff --git a/RealtekPCIeCardReader/IOSDCard.hpp b/RealtekPCIeCardReader/IOSDCard.hpp new file mode 100644 index 0000000..7a1f4c3 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDCard.hpp @@ -0,0 +1,1444 @@ +// +// IOSDCard.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/3/21. +// + +#ifndef IOSDCard_hpp +#define IOSDCard_hpp + +#include +#include +#include "Utilities.hpp" +#include "BitOptions.hpp" + +#define UNSTUFF_BITS(resp,start,size) \ + ({ \ + const int __size = size; \ + const UInt32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \ + const int __off = 3 - ((start) / 32); \ + const int __shft = (start) & 31; \ + UInt32 __res; \ + \ + __res = resp[__off] >> __shft; \ + if (__size + __shft > 32) \ + __res |= resp[__off-1] << ((32 - __shft) % 32); \ + __res & __mask; \ + }) + +/// Forward declaration +class IOSDHostDriver; + +/// Operating condition register value +struct OCR +{ + /// Register value as a place holder + UInt32 value; + + /// Bit 24 is set if the host requests the card to switch to 1.8V + static constexpr UInt32 kRequest1d8V = 1 << 24; + + /// Bit 24 is set if the card accepts 1.8V switching + static constexpr UInt32 kAccepted1d8V = 1 << 24; + + /// Bit 28 is set if the host can supply more than 150mA + static constexpr UInt32 kSDXCPowerControl = 1 << 28; + + /// Bit 30 is set if the card is SDHC/SDXC and clear if the card is SDSC + static constexpr UInt32 kCardCapacityStatus = 1 << 30; +}; + +/// Card identification data (16 bytes) +struct PACKED CID +{ + /// The manufacturer ID + UInt8 manufacturer; + + /// The OEM application ID + UInt16 oem; + + /// The product name + SInt8 name[5]; + + /// The product revision (hardware) + UInt8 hwrevision: 4; + + /// The product revision (firmware) + UInt8 fwrevision: 4; + + /// The product serial number + UInt32 serial; + + /// Reserved 4 bits + UInt8 reserved: 4; + + /// Manufacturing date (year) + UInt8 year: 8; + + /// Manufacturing date (month) + UInt8 month: 4; + + /// CRC7 checksum and the end bit + UInt8 end; + + /// Get the manufacturing year + inline UInt32 getYear() const + { + return this->year + 2000; + } + + /// Get a non-null vendor string + inline const char* getVendorString() const + { + // CID List: https://www.cameramemoryspeed.com/sd-memory-card-faq/reading-sd-card-cid-serial-psn-internal-numbers/ + switch (this->manufacturer) + { + case 0x01: + { + return "Panasonic"; + } + + case 0x02: + { + return "Toshiba"; + } + + case 0x03: + { + return "SanDisk"; + } + + case 0x1b: + { + return "Samsung"; + } + + case 0x1d: + { + return "ADATA"; + } + + case 0x27: + { + return "Phison"; + } + + case 0x28: + { + return "Lexar"; + } + + case 0x31: + { + return "Silicon Power"; + } + + case 0x41: + { + return "Kingston"; + } + + case 0x74: + { + return "Transcend"; + } + + case 0x76: + { + return "Patriot"; + } + + case 0x82: + { + return "Sony"; + } + + default: + { + return "Unknown"; + } + } + } + + /// + /// Decode from the given raw data + /// + /// @param data An array of 4 integers encoded in big endian + /// @param pcid The parsed card identification data + /// @return `true` on success, `false` otherwise. + /// + static bool decode(const UInt32* data, CID& pcid) + { + pcid.manufacturer = UNSTUFF_BITS(data, 120, 8); + pcid.oem = UNSTUFF_BITS(data, 104, 16); + pcid.name[0] = UNSTUFF_BITS(data, 96, 8); + pcid.name[1] = UNSTUFF_BITS(data, 88, 8); + pcid.name[2] = UNSTUFF_BITS(data, 80, 8); + pcid.name[3] = UNSTUFF_BITS(data, 72, 8); + pcid.name[4] = UNSTUFF_BITS(data, 64, 8); + pcid.hwrevision = UNSTUFF_BITS(data, 60, 4); + pcid.fwrevision = UNSTUFF_BITS(data, 56, 4); + pcid.serial = UNSTUFF_BITS(data, 24, 32); + pcid.year = UNSTUFF_BITS(data, 12, 8); + pcid.month = UNSTUFF_BITS(data, 8, 4); + + return true; + } +}; + +/// SD configuration register value (8 bytes) +struct PACKED SCR +{ + /// The structure version + UInt8 version: 4; + + /// SD card specification version + UInt8 spec: 4; + + enum Spec + { + /// SD Specification 1.0 - 1.01 + kVersion1x = 0, + + /// SD Specification 1.10 + kVersion1d1x = 1, + + /// SD Specification 2.00 - 3.0X + kVersion2x = 2, + }; + + /// The data status after erase + UInt8 dataStatusAfterErase: 1; + + /// The security specification version + UInt8 security: 3; + + /// The bus widths supported by the card (Bit 0: 1 bit, Bit 2: 4 bits) + UInt8 busWidths: 4; + + enum BusWidth + { + k1Bit = 1 << 0, + k4Bit = 1 << 2, + }; + + /// SD card specification version 3.00+ + UInt8 spec3: 1; + + /// Extended security support + UInt8 extendedSecurity: 4; + + /// SD card specification version 4.00+ + UInt8 spec4: 1; + + /// SD card specification version 5.00+ + UInt8 spec5: 4; + + /// Reserved 2 bits + UInt8 reserved0: 2; + + /// Supported commands (CMD58/59) + bool supportsCMD5859: 1; + + /// Supported commands (CMD48/49) + bool supportsCMD4849: 1; + + /// Supported commands (CMD23) + bool supportsCMD23: 1; + + /// Supported commands (CMD20) + bool supportsCMD20: 1; + + /// Reserved 32 bits for manufacturer usage + UInt32 reserved1; + + /// + /// Decode from the given raw data + /// + /// @param data An array of 2 integers encoded in big endian + /// @param pscr The parsed SD status register value + /// @return `true` on success, `false` otherwise. + /// + static bool decode(const UInt8* data, SCR& pscr) + { + pscr.version = (data[0] & 0xF0) >> 4; + + if (pscr.version != 0) + { + printf("Unrecogized SCR struct version 0x%02x.\n", pscr.version); + + return false; + } + + pscr.busWidths = data[1] & 0x0F; + + if (!BitOptions(pscr.busWidths).contains(BusWidth::k1Bit) || + !BitOptions(pscr.busWidths).contains(BusWidth::k4Bit)) + { + printf("All cards should set at least bit 0 and bit 2 in the bus widths field (= 0x%x).\n", pscr.busWidths); + + return false; + } + + pscr.spec = data[0] & 0x0F; + pscr.dataStatusAfterErase = (data[1] & 0x80) >> 7; + pscr.security = (data[1] & 0x70) >> 4; + pscr.spec3 = (data[2] & 0x80) >> 7; + pscr.extendedSecurity = (data[2] & 0x78) >> 3; + pscr.spec4 = (data[2] & 0x04) >> 2; + pscr.spec5 = (data[2] & 0x03) << 2; + pscr.spec5 |= (data[3] & 0xC0) >> 6; + pscr.supportsCMD5859 = (data[3] & 0x0F) >> 3; + pscr.supportsCMD4849 = (data[3] & 0x0F) >> 2; + pscr.supportsCMD23 = (data[3] & 0x0F) >> 1; + pscr.supportsCMD20 = (data[3] & 0x0F) >> 0; + + return true; + } +}; + +/// Card specification level +struct PACKED SPEC +{ + UInt8 spec, spec3, spec4, spec5; +}; + +/// SD status register value (64 bytes) +struct PACKED SSR +{ + /// Bus width (0: 1 bit, 2: 4 bits) + UInt8 busWidth: 2; + + /// `True` if the card is in secured mode + bool securedMode: 1; + + /// Reserved 7 bits for security functions + UInt8 reserved0: 7; + + /// Reserved 6 bits + UInt8 reserved1: 6; + + /// SD card type + UInt16 cardType; + + /// Size of protected area + UInt32 protectedAreaSize; + + /// Speed class of the card + UInt8 speedClass; + + /// Performance of move indicatedf by 1MB/s step + UInt8 movePerformance; + + /// Size of an allocation unit + UInt8 auSize: 4; + + /// Reserved 4 bits + UInt8 reserved2: 4; + + /// Number of AUs to be erased at a time + UInt16 eraseSize; + + /// Timeout value for erasing areaa + UInt8 eraseTimeout: 6; + + /// Fixed offset value added to erase time + UInt8 eraseOffset: 2; + + /// Speed grade for UHS mode + UInt8 uhsSpeedGrade: 4; + + /// Size of AU for UHS mode + UInt8 uhsAuSize: 4; + + /// Video speed class + UInt8 videoSpeedClass; + + /// Reserved 6 bits + UInt16 reserved3: 6; + + /// Size of AU for video speed class + UInt16 vscAuSize: 10; + + /// Suspension address + UInt32 suspensionAddress: 22; + + /// Reserved 6 bits + UInt32 reserved4: 6; + + /// Application performance class value + UInt32 appPerformanceClass: 4; + + /// Support for Performance Enhancement functionalities + UInt8 performanceEnhance; + + /// Reserved 14 bits + UInt16 reserved5: 14; + + /// Discard support + bool supportsDiscard: 1; + + /// Full user area logical erase support + bool supportsFULE: 1; + + /// Reserved 39 bytes for manufacturer + UInt8 reserved6[39]; +}; + +/// Card specific data (Version 1.0) (16 bytes) +struct PACKED CSDV1 +{ + // =============== + // | Common | + // =============== + + /// The structure version + UInt8 version: 2; + + /// Reserved 6 bits + UInt8 reserved0: 6; + + /// Data read access time - 1 (Reserved 1 bit) + UInt8 reserved1: 1; + + /// Data read access time - 1 (Time value) + UInt8 taacTimeValue: 4; + + /// Data read access time - 1 (Time unit) + UInt8 taacTimeUnit: 3; + + /// Data read access time - 2 in clock cycles + UInt8 nasc; + + /// Maximum data transfer rate (Reserved 1 bit) + UInt8 reservedDTR: 1; + + /// Maximum data transfer rate (Transfer rate value) + UInt8 maxTransferRateValue: 4; + + /// Maximum data transfer rate (Transfer rate unit) + UInt8 maxTransferRateUnit: 3; + + /// Card command classes + UInt16 cardCommandClasses: 12; + + /// Maximum read data block length + UInt16 maxReadDataBlockLength: 4; + + /// `True` if reading partial blocks is allowed + bool isPartialBlockReadAllowed: 1; + + /// Write block misalignment + UInt8 writeBlockMisalignment: 1; + + /// Read block misalignment + UInt8 readBlockMisalignment: 1; + + /// `True` if DSR is implemented + bool isDSRImplemented: 1; + + // =============== + // | V1 Specific | + // =============== + + /// Reserved 2 bits + UInt8 reserved2: 2; + + /// Device size + UInt16 deviceSize: 12; + + /// Maximum read current @ the minimum VDD + UInt8 maxReadCurrentAtVDDMin: 3; + + /// Maximum read current @ the maximum VDD + UInt8 maxReadCurrentAtVDDMax: 3; + + /// Maximum write current @ the minimum VDD + UInt8 maxWriteCurrentAtVDDMin: 3; + + /// Maximum write current @ the maximum VDD + UInt8 maxWriteCurrentAtVDDMax: 3; + + /// Device size multiplier + UInt8 deviceSizeMultiplier: 3; + + // =============== + // | Common | + // =============== + + /// `True` if erasing a single block is enabled + bool isEraseSingleBlockEnabled: 1; + + /// Erase sector size + UInt8 eraseSectorSize: 7; + + /// Write protect group size + UInt8 writeProtectGroupSize: 7; + + /// `True` if write protect group is enabled + bool isWriteProtectGroupEnabled: 1; + + /// Reserved 2 bits + UInt8 reserved3: 2; + + /// Write speed factor + UInt8 writeSpeedFactor: 3; + + /// Maximum write data block length + UInt8 maxWriteDataBlockLength: 4; + + /// `True` if writing partial blocks is allowed + bool isPartialBlockWriteAllowed: 1; + + /// Reserved 5 bits + UInt8 reserved4: 5; + + /// File format group + UInt8 fileFormatGroup: 1; + + /// Copy flag + UInt8 copyFlag: 1; + + /// Permanent write protection + bool permanentWriteProtection: 1; + + /// Temporary write protection + bool temporaryWriteProtection: 1; + + /// File format + UInt8 fileFormat: 2; + + /// Reserved 2 bits + UInt8 reserved5: 2; + + /// CRC7 checksum and the end bit + UInt8 end; +}; + +/// Card specific data (Version 2.0) (16 bytes) +struct PACKED CSDV2 +{ + // =============== + // | Common | + // =============== + + /// The structure version + UInt8 version: 2; + + /// Reserved 6 bits + UInt8 reserved0: 6; + + /// Data read access time - 1 (Reserved 1 bit) + UInt8 reserved1: 1; + + /// Data read access time - 1 (Time value) + UInt8 taacTimeValue: 4; + + /// Data read access time - 1 (Time unit) + UInt8 taacTimeUnit: 3; + + /// Data read access time - 2 in clock cycles + UInt8 nasc; + + /// Maximum data transfer rate (Reserved 1 bit) + UInt8 reservedDTR: 1; + + /// Maximum data transfer rate (Transfer rate value) + UInt8 maxTransferRateValue: 4; + + /// Maximum data transfer rate (Transfer rate unit) + UInt8 maxTransferRateUnit: 3; + + /// Card command classes + UInt16 cardCommandClasses: 12; + + /// Maximum read data block length + UInt16 maxReadDataBlockLength: 4; + + /// `True` if reading partial blocks is allowed + bool isPartialBlockReadAllowed: 1; + + /// Write block misalignment + UInt8 writeBlockMisalignment: 1; + + /// Read block misalignment + UInt8 readBlockMisalignment: 1; + + /// `True` if DSR is implemented + bool isDSRImplemented: 1; + + // =============== + // | V2 Specific | + // =============== + + /// Reserved 6 bits + UInt8 reserved2: 6; + + /// Device size + UInt32 deviceSize: 22; + + /// Reserved 1 bit + UInt8 reserved21: 1; + + // =============== + // | Common | + // =============== + + /// `True` if erasing a single block is enabled + bool isEraseSingleBlockEnabled: 1; + + /// Erase sector size + UInt8 eraseSectorSize: 7; + + /// Write protect group size + UInt8 writeProtectGroupSize: 7; + + /// `True` if write protect group is enabled + bool isWriteProtectGroupEnabled: 1; + + /// Reserved 2 bits + UInt8 reserved3: 2; + + /// Write speed factor + UInt8 writeSpeedFactor: 3; + + /// Maximum write data block length + UInt8 maxWriteDataBlockLength: 4; + + /// `True` if writing partial blocks is allowed + bool isPartialBlockWriteAllowed: 1; + + /// Reserved 5 bits + UInt8 reserved4: 5; + + /// File format group + UInt8 fileFormatGroup: 1; + + /// Copy flag + UInt8 copyFlag: 1; + + /// Permanent write protection + bool permanentWriteProtection: 1; + + /// Temporary write protection + bool temporaryWriteProtection: 1; + + /// File format + UInt8 fileFormat: 2; + + /// Reserved 2 bits + UInt8 reserved5: 2; + + /// CRC7 checksum and the end bit + UInt8 end; +}; + +/// Card specific data (Version 3.0) (16 bytes) +struct PACKED CSDV3 +{ + // =============== + // | Common | + // =============== + + /// The structure version + UInt8 version: 2; + + /// Reserved 6 bits + UInt8 reserved0: 6; + + /// Data read access time - 1 (Reserved 1 bit) + UInt8 reserved1: 1; + + /// Data read access time - 1 (Time value) + UInt8 taacTimeValue: 4; + + /// Data read access time - 1 (Time unit) + UInt8 taacTimeUnit: 3; + + /// Data read access time - 2 in clock cycles + UInt8 nasc; + + /// Maximum data transfer rate (Reserved 1 bit) + UInt8 reservedDTR: 1; + + /// Maximum data transfer rate (Transfer rate value) + UInt8 maxTransferRateValue: 4; + + /// Maximum data transfer rate (Transfer rate unit) + UInt8 maxTransferRateUnit: 3; + + /// Card command classes + UInt16 cardCommandClasses: 12; + + /// Maximum read data block length + UInt16 maxReadDataBlockLength: 4; + + /// `True` if reading partial blocks is allowed + bool isPartialBlockReadAllowed: 1; + + /// Write block misalignment + UInt8 writeBlockMisalignment: 1; + + /// Read block misalignment + UInt8 readBlockMisalignment: 1; + + /// `True` if DSR is implemented + bool isDSRImplemented: 1; + + // =============== + // | V3 Specific | + // =============== + + /// Device size + UInt32 deviceSize: 28; + + /// Reserved 1 bit + UInt8 reserved2: 1; + + // =============== + // | Common | + // =============== + + /// `True` if erasing a single block is enabled + bool isEraseSingleBlockEnabled: 1; + + /// Erase sector size + UInt8 eraseSectorSize: 7; + + /// Write protect group size + UInt8 writeProtectGroupSize: 7; + + /// `True` if write protect group is enabled + bool isWriteProtectGroupEnabled: 1; + + /// Reserved 2 bits + UInt8 reserved3: 2; + + /// Write speed factor + UInt8 writeSpeedFactor: 3; + + /// Maximum write data block length + UInt8 maxWriteDataBlockLength: 4; + + /// `True` if writing partial blocks is allowed + bool isPartialBlockWriteAllowed: 1; + + /// Reserved 5 bits + UInt8 reserved4: 5; + + /// File format group + UInt8 fileFormatGroup: 1; + + /// Copy flag + UInt8 copyFlag: 1; + + /// Permanent write protection + bool permanentWriteProtection: 1; + + /// Temporary write protection + bool temporaryWriteProtection: 1; + + /// File format + UInt8 fileFormat: 2; + + /// Reserved 2 bits + UInt8 reserved5: 2; + + /// CRC7 checksum and the end bit + UInt8 end; +}; + +/// Card specific data (Raw Data) +union CSDVX +{ + UInt8 data[16]; + CSDV1 v1; + CSDV2 v2; + CSDV3 v3; +}; + +/// Card specific data (Parsed Data) +struct CSD +{ + /// CSD struct version + enum Version + { + k1 = 0, + k2 = 1, + k3 = 2, + }; + + /// Data read access time in nanoseconds + UInt32 taacTimeNanosecs; + + /// Data read access time in number of clocks + UInt32 taacTimeClocks; + + /// Card command classes + UInt32 cardCommandClasses; + + /// Enumerate all SD command classes + enum CommandClass + { + /// Class 0: Basic protocol functions + /// CMD0, 1, 2, 3, 4, 7, 9, 10, 12, 13, 15 + kBasic = 1 << 0, + + /// Class 1: Stream read commands + /// CMD11 + kStreamRead = 1 << 1, + + /// Class 2: Block read commands + /// CMD16, 17, 18 + kBlockRead = 1 << 2, + + /// Class 3: Stream write commands + /// CMD20 + kStreamWrite = 1 << 3, + + /// Class 4: Block write commands + /// CMD16, 24, 25, 26, 27 + kBlockWrite = 1 << 4, + + /// Class 5: Ability to erase blocks + /// CMD32, 33, 34, 35, 36, 37, 38, 39 + kErase = 1 << 5, + + /// Class 6: Ability to write protect blocks + /// CMD28, 29, 30 + kWriteProtect = 1 << 6, + + /// Class 7: Ability to lock down the card + /// CMD16, 42 + kLockCard = 1 << 7, + + /// Class 8: Application specific commands + /// CMD55, 56, 57, ACMD* + kAppSpecific = 1 << 8, + + /// Class 9: SD I/O mode + /// CMD5, 39, 40, 52, 53 + kIOMode = 1 << 9, + + /// Class 10: High Speed Switch + /// CMD6, 11, 34, 35, 36, 37, 50 + kSwitch = 1 << 10, + }; + + /// Maximum data transfer rate (in bps or Hz) + UInt32 maxDataTransferRate; + + /// Card capacity (in number of read blocks) + UInt32 capacity; + + /// Read block length is 2^ReadBlockLength + UInt32 readBlockLength; + + /// Write block length + UInt32 writeBlockLength; + + /// Write speed factor + UInt32 writeSpeedFactor; + + /// Erase size in number of sectors + UInt32 eraseSize; + + /// Device size + UInt32 deviceSize; + + /// True if reading a partial block is allowed + bool canReadPartialBlock; + + /// True if reading a block that spreads over more than one physical block is allowed + bool canReadMisalignedBlock; + + /// True if writing a partial is allowed + bool canWritePartialBlock; + + /// True if writing a block that spreads over more than one physical block is allowed + bool canWriteMisalignedBlock; + + /// True if the driver stage register is implemented + bool isDSRImplemented; + + /// `True` if the card is block-addressed (SDHC/SDXC) + /// @note This field is derived from the CSD struct version. + bool isBlockAddressed; + + /// `True` if the card has extended capacity (SDXC) + /// @note This field is derived from the device size. + bool hasExtendedCapacity; + + /// Data access time (TAAC): Time unit in nanoseconds + static constexpr UInt32 kTAACTimeUnits[] = + { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, + }; + + /// Data access time (TAAC): Time value multipliers (scaled to 10x) + static constexpr UInt32 kTAACTimeValues[] = + { + 00, 10, 12, 13, 15, 20, 25, 30, // 1.0 - 3.0x + 35, 40, 45, 50, 55, 60, 70, 80, // 3.5 - 8.0x + }; + + /// Maximum data transfer rate (DTR): Rate unit (scaled to 1/10x) + static constexpr UInt32 kDTRUnits[] = + { + 10000, // 100 Kbps + 100000, // 1 Mbps + 1000000, // 10 Mbps + 10000000, // 100 Mbps + 0, 0, 0, 0, + }; + + /// Maximum data transfer rate (DTR): Rate multiplier (scaled to 10x) + static constexpr UInt32 kDTRValues[] = + { + 00, 10, 12, 13, 15, 20, 25, 30, // 1.0 - 3.0x + 35, 40, 45, 50, 55, 60, 70, 80, // 3.5 - 8.0x + }; + + /// + /// Decode from the given raw data + /// + /// @param data An array of 4 integers encoded in big endian + /// @param pcsd The parsed card specific data on return + /// @return `true` on success, `false` otherwise. + /// + static bool decode(const UInt32* data, CSD& pcsd) + { + UInt32 version = UNSTUFF_BITS(data, 126, 2); + + UInt32 unit, value; + + switch (version) + { + // SDSC cards + case CSD::Version::k1: + { + pcsd.isBlockAddressed = false; + + pcsd.hasExtendedCapacity = false; + + value = UNSTUFF_BITS(data, 115, 4); + unit = UNSTUFF_BITS(data, 112, 3); + pcsd.taacTimeNanosecs = (kTAACTimeUnits[unit] * kTAACTimeValues[value] + 9) / 10; + + pcsd.taacTimeClocks = UNSTUFF_BITS(data, 104, 8) * 100; + + value = UNSTUFF_BITS(data, 99, 4); + unit = UNSTUFF_BITS(data, 96, 3); + pcsd.maxDataTransferRate = kDTRUnits[unit] * kDTRValues[value]; + + pcsd.cardCommandClasses = UNSTUFF_BITS(data, 84, 12); + + pcsd.capacity = (1 + UNSTUFF_BITS(data, 62, 12)) << (UNSTUFF_BITS(data, 47, 3) + 2); + + pcsd.readBlockLength = UNSTUFF_BITS(data, 80, 4); + + pcsd.canReadPartialBlock = UNSTUFF_BITS(data, 79, 1); + + pcsd.canWriteMisalignedBlock = UNSTUFF_BITS(data, 78, 1); + + pcsd.canReadMisalignedBlock = UNSTUFF_BITS(data, 77, 1); + + pcsd.isDSRImplemented = UNSTUFF_BITS(data, 76, 1); + + pcsd.writeSpeedFactor = UNSTUFF_BITS(data, 26, 3); + + pcsd.writeBlockLength = UNSTUFF_BITS(data, 22, 4); + + pcsd.canWritePartialBlock = UNSTUFF_BITS(data, 21, 1); + + if (UNSTUFF_BITS(data, 46, 1)) + { + pcsd.eraseSize = 1; + } + else if (pcsd.writeBlockLength >= 9) + { + pcsd.eraseSize = UNSTUFF_BITS(data, 39, 7) + 1; + + pcsd.eraseSize <<= (pcsd.writeBlockLength - 9); + } + + break; + } + + // SDHC / SDXC cards + case CSD::Version::k2: + { + pcsd.isBlockAddressed = true; + + pcsd.taacTimeNanosecs = 0; + + pcsd.taacTimeClocks = 0; + + value = UNSTUFF_BITS(data, 99, 4); + unit = UNSTUFF_BITS(data, 96, 3); + pcsd.maxDataTransferRate = kDTRUnits[unit] * kDTRValues[value]; + + pcsd.cardCommandClasses = UNSTUFF_BITS(data, 84, 12); + + pcsd.deviceSize = UNSTUFF_BITS(data, 48, 22); + + pcsd.hasExtendedCapacity = pcsd.deviceSize >= 0xFFFF; + + pcsd.capacity = (1 + pcsd.deviceSize) << 10; + + pcsd.readBlockLength = 9; + + pcsd.canReadPartialBlock = false; + + pcsd.canWriteMisalignedBlock = false; + + pcsd.canReadMisalignedBlock = false; + + pcsd.writeSpeedFactor = 4; // Unused + + pcsd.writeBlockLength = 9; + + pcsd.canWritePartialBlock = false; + + pcsd.eraseSize = 1; + + break; + } + + case CSD::Version::k3: + { + perr("SDUC card is not supported at this moment."); + + return false; + } + + default: + { + perr("Unrecognized CSD structure version %d.", version); + + return false; + } + } + + return true; + } +}; + +/// High speed switch capabilities +struct SwitchCaps +{ + /// The maximum clock frequency for the current bus speed mode + struct MaxClockFrequencies + { + /// The maximum clock frequency for the high speed mode + UInt32 highSpeedMode; + + /// The maximum clock frequency for the ultra high speed mode + UInt32 ultraHighSpeedMode; + + } maxClockFrequencies; + + /// Maximum clock frequency for each bus speed mode + enum MaxClockFrequency: UInt32 + { + kClockDefaultSpeed = MHz2Hz(25), + kClockHighSpeed = MHz2Hz(50), + kClockUHSSDR12 = MHz2Hz(25), + kClockUHSSDR25 = MHz2Hz(50), + kClockUHSDDR50 = MHz2Hz(50), + kClockUHSSDR50 = MHz2Hz(100), + kClockUHSSDR104 = MHz2Hz(208), + }; + + /// Supported bus modes (SD 3.0+) + UInt32 sd3BusMode; + + /// Enumerates all possible bus speeds + /// The value is passed to the CMD6 + enum BusSpeed: UInt32 + { + kSpeedUHSSDR12 = 0, + kSpeedUHSSDR25 = 1, + kSpeedUHSSDR50 = 2, + kSpeedUHSSDR104 = 3, + kSpeedUHSDDR50 = 4, + kSpeedHighSpeed = 1, + }; + + /// Enumerates all possible bus modes + /// Use `BitOptions(busMode).contains()` to check whether a mode is supported + enum BusMode: UInt32 + { + kModeUHSSDR12 = 1 << BusSpeed::kSpeedUHSSDR12, + kModeUHSSDR25 = 1 << BusSpeed::kSpeedUHSSDR25, + kModeUHSSDR50 = 1 << BusSpeed::kSpeedUHSSDR50, + kModeUHSSDR104 = 1 << BusSpeed::kSpeedUHSSDR104, + kModeUHSDDR50 = 1 << BusSpeed::kSpeedUHSDDR50, + kModeHighSpeed = 1 << BusSpeed::kSpeedHighSpeed, + }; + + /// Driver strength (SD 3.0+) + UInt32 sd3DriverType; + + /// Enumerates all possible driver types + enum DriverType: UInt32 + { + kTypeB = 0x01, + kTypeA = 0x02, + kTypeC = 0x04, + kTypeD = 0x08, + }; + + /// The current limit at the host signal voltage level + UInt32 sd3MaxCurrent; + + /// Enumerates all possible current limits + /// The value is passed to the CMD6 + enum SetCurrentLimit: UInt32 + { + kCurrentLimit200mA = 0, + kCurrentLimit400mA = 1, + kCurrentLimit600mA = 2, + kCurrentLimit800mA = 3, + }; + + /// Enumerates all possible maximum currents + /// Use `BitOptions(maxCurrent).contains()` to check whether a current is supported + enum MaxCurrent + { + kMaxCurrent200mA = 1 << SetCurrentLimit::kCurrentLimit200mA, + kMaxCurrent400mA = 1 << SetCurrentLimit::kCurrentLimit400mA, + kMaxCurrent600mA = 1 << SetCurrentLimit::kCurrentLimit600mA, + kMaxCurrent800mA = 1 << SetCurrentLimit::kCurrentLimit800mA, + }; +}; + +static_assert(sizeof(CID) == 16, "CID should be 16 bytes long."); +static_assert(sizeof(SCR) == 8, "SCR should be 8 bytes long."); +static_assert(sizeof(SSR) == 64, "SSR should be 64 bytes long."); +static_assert(sizeof(CSDVX) == 16, "CSD X.0 should be 16 bytes long."); +static_assert(sizeof(CSDV1) == 16, "CSD 1.0 should be 16 bytes long."); +static_assert(sizeof(CSDV2) == 16, "CSD 2.0 should be 16 bytes long."); +static_assert(sizeof(CSDV3) == 16, "CSD 3.0 should be 16 bytes long."); + +/// Represents a generic SD(SC/HC/XC) card +class IOSDCard: public IOService +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareDefaultStructors(IOSDCard); + + using super = IOService; + + /// The host driver + IOSDHostDriver* driver; + + /// The card identification data + CID cid; + + /// The card specification data + CSD csd; + + /// SD configuration data + SCR scr; + + /// SD status data + SSR ssr; + + /// Switch capabilities + SwitchCaps switchCaps; + + /// The card relative address + UInt32 rca; + + /// + /// Initialize the card with the given OCR register value + /// + /// @param driver The host driver + /// @param ocr The current operating condition register value + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces `mmc_sd_init_card()` defined in `sd.c`. + /// + bool init(IOSDHostDriver* driver, UInt32 ocr); + + /// + /// [Helper] Initialize the card with Default Speed Mode enabled + /// + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces the default speed portion of `mmc_sd_init_card()` defined in `sd.c`. + /// + bool initDefaultSpeedMode(); + + /// + /// [Helper] Initialize the card with High Speed Mode enabled + /// + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces the high speed portion of `mmc_sd_init_card()` defined in `sd.c`. + /// + bool initHighSpeedMode(); + + /// + /// [Helper] Initialize the card with UHS-I Mode enabled + /// + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces `mmc_sd_init_uhs_card()` defined in `sd.c`. + /// + bool initUltraHighSpeedMode(); + + /// + /// [Helper] Read the card switch capabilities + /// + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces `mmc_read_switch()` defined in `sd.c`. + /// + bool readSwitchCapabilities(); + + /// + /// [Helper] Enable the 4-bit wide bus for data transfer + /// + /// @return `true` on success, `false` otherwise. + /// + bool enable4BitWideBus(); + + /// + /// [Helper] Enable the signal voltage for the UHS-I mode + /// + /// @param ocr The current operating condition register value + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces `mmc_set_uhs_voltage()` defined in `core.c`. + /// + bool enableUltraHighSpeedSignalVoltage(UInt32 ocr); + + /// + /// [Helper] Select the bus speed for the UHS-I mode + /// + /// @return The bus speed supported by both the card and the host. + /// @note Port: This function replaces `sd_update_bus_speed_mode()` defined in `sd.c`. + /// + SwitchCaps::BusSpeed selectUltraHighSpeedBusSpeed(); + + + + + + + bool setUHSDriverType(SwitchCaps::BusSpeed busSpeed); + + + + + /// + /// [Helper] Set the current limit for the UHS-I card + /// + /// @param busSpeed The bus speed + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces `sd_set_current_limit()` defined in `sd.c`. + /// @note This function is a part of the UHS-I card initialization routine `initUltraHighSpeedMode()`. + /// + bool setUHSCurrentLimit(SwitchCaps::BusSpeed busSpeed); + + /// + /// [Helper] Set the bus speed for the UHS-I card + /// + /// @param busSpeed The bus speed + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces `sd_set_bus_speed_mode()` defined in `sd.c`. + /// @note This function is a part of the UHS-I card initialization routine `initUltraHighSpeedMode()`. + /// + bool setUHSBusSpeedMode(SwitchCaps::BusSpeed busSpeed); + +public: + /// Get the card identification data + inline const CID& getCID() + { + return this->cid; + } + + /// Get the card specification data + inline const CSD& getCSD() + { + return this->csd; + } + + /// Get the SD configuration data + inline const SCR& getSCR() + { + return this->scr; + } + + /// Get the SD status data + inline const SSR& getSSR() + { + return this->ssr; + } + + /// Get the card relative address + inline UInt32 getRCA() + { + return this->rca; + } + + /// Get the card type + inline const char* getCardType() + { + if (!this->csd.isBlockAddressed) + { + return "SDSC"; + } + + if (!this->csd.hasExtendedCapacity) + { + return "SDHC"; + } + else + { + return "SDXC"; + } + } + + /// Check whether the card matches the given specification version + inline bool matchesSpecificationLevel(const SPEC& spec) + { + return this->scr.spec == spec.spec && + this->scr.spec3 == spec.spec3 && + this->scr.spec4 == spec.spec4 && + this->scr.spec5 == spec.spec5; + } + + /// Get the specification version string + inline const char* getSpecificationVersion() + { + // SPEC Fields + static constexpr SPEC kSpecTable[] = + { + { 0, 0, 0, 0 }, // Version 1.0 and 1.01 + { 1, 0, 0, 0 }, // Version 1.10 + { 2, 0, 0, 0 }, // Version 2.00 + { 2, 1, 0, 0 }, // Version 3.0X + { 2, 1, 1, 0 }, // Version 4.XX + { 2, 1, 0, 1 }, // Version 5.XX + { 2, 1, 1, 1 }, // Version 5.XX + { 2, 1, 0, 2 }, // Version 6.XX + { 2, 1, 1, 2 }, // Version 6.XX + { 2, 1, 0, 3 }, // Version 7.XX + { 2, 1, 1, 3 }, // Version 7.XX + { 2, 1, 0, 4 }, // Version 8.XX + { 2, 1, 1, 4 }, // Version 8.XX + }; + + // SPEC Version Strings + static constexpr const char* kSpecString[] = + { + "1.00", + "1.10", + "2.00", + "3.00", + "4.00", + "5.00", + "5.00", + "6.00", + "6.00", + "7.00", + "7.00", + "8.00", + "8.00", + }; + + static_assert(arrsize(kSpecTable) == arrsize(kSpecString), "Inconsistent SPEC tables."); + + for (auto index = 0; index < arrsize(kSpecTable); index += 1) + { + if (this->matchesSpecificationLevel(kSpecTable[index])) + { + return kSpecString[index]; + } + } + + return "Unknown"; + } + + /// + /// Get the card product name + /// + /// @param name A non-null buffer that can hold at least 8 bytes. + /// @param length The buffer length + /// @return `true` on success, `false` if the buffer is NULL or too small. + /// + inline bool getCardName(char* name, IOByteCount length) + { + if (name == nullptr || length < 8) + { + return false; + } + + bzero(name, length); + + memcpy(name, this->cid.name, sizeof(this->cid.name)); + + return true; + } + + /// + /// Get the card revision value + /// + /// @param revision A non-null buffer that can hold at least 8 bytes. + /// @param length The buffer length + /// @return `true` on success, `false` if the buffer is NULL or too small. + /// + inline bool getCardRevision(char* revision, IOByteCount length) + { + if (revision == nullptr || length < 8) + { + return false; + } + + bzero(revision, length); + + snprintf(revision, length, "%d.%d", this->cid.hwrevision, this->cid.fwrevision); + + return true; + } + + /// + /// Get the card manufacture date + /// + /// @param date A non-null buffer that can hold at least 8 bytes. + /// @param length The buffer length + /// @return `true` on success, `false` if the buffer is NULL or too small. + /// + inline bool getCardProductionDate(char* date, IOByteCount length) + { + if (date == nullptr || length < 8) + { + return false; + } + + bzero(date, length); + + snprintf(date, length, "%04d.%02d", this->cid.getYear(), this->cid.month); + + return true; + } + + /// + /// Get the card characteristics + /// + /// @return A dictionary that contains card characteristics which can be recognized by the System Profiler. + /// @note The caller is responsible for releasing the returned dictionary. + /// + OSDictionaryPtr getCardCharacteristics(); + + /// + /// Create the card and initialize it with the given OCR value + /// + /// @param driver The non-null host driver + /// @param ocr The OCR value that contains the voltage level supported by the host and the card + /// @return A non-null card instance on success, `nullptr` otherwise. + /// + static IOSDCard* createWithOCR(IOSDHostDriver* driver, UInt32 ocr); +}; + +#endif /* IOSDCard_hpp */ diff --git a/RealtekPCIeCardReader/IOSDCardEventSource.cpp b/RealtekPCIeCardReader/IOSDCardEventSource.cpp new file mode 100644 index 0000000..84442a2 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDCardEventSource.cpp @@ -0,0 +1,84 @@ +// +// IOSDCardEventSource.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/9/21. +// + +#include "IOSDCardEventSource.hpp" + +#include "Debug.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(IOSDCardEventSource, IOEventSource); + +/// +/// Check whether an action needs to perform to handle the card event +/// +/// @return `false` always. The owner must explictly invoke `IOSDCardEventSource::enable()` to trigger another action. +/// +bool IOSDCardEventSource::checkForWork() +{ + pinfo("The card event source is invoked by the processor work loop."); + + if (this->action == nullptr) + { + perr("Detected inconsistency: The action routine should never be null."); + + return false; + } + + if (!this->enabled) + { + pinfo("The event source is disabled."); + + return false; + } + + // Acknowledge the card event + this->disable(); + + pinfo("Processing the card event..."); + + (*this->action)(this->owner); + + pinfo("The card event has been processed."); + + return false; +} + +/// +/// Create a card event source with the given action +/// +/// @param owner Owner of the returned instance; the first parameter of the action routine +/// @param action A non-null action to process the card insertion or removal event +/// @return A non-null event source on success, `nullptr` otherwise. +/// +IOSDCardEventSource* IOSDCardEventSource::createWithAction(OSObject* owner, IOEventSource::Action action) +{ + if (action == nullptr) + { + perr("The action routine must be non-null."); + + return nullptr; + } + + auto instance = OSTypeAlloc(IOSDCardEventSource); + + if (instance == nullptr) + { + return nullptr; + } + + if (!instance->init(owner, action)) + { + instance->release(); + + return nullptr; + } + + return instance; +} diff --git a/RealtekPCIeCardReader/IOSDCardEventSource.hpp b/RealtekPCIeCardReader/IOSDCardEventSource.hpp new file mode 100644 index 0000000..0ef5ca8 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDCardEventSource.hpp @@ -0,0 +1,39 @@ +// +// IOSDCardEventSource.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/9/21. +// + +#ifndef IOSDCardEventSource_hpp +#define IOSDCardEventSource_hpp + +#include + +/// An event source to signal the workloop to process the card insertion or removal event +class IOSDCardEventSource: public IOEventSource +{ + /// Constructors & Destructors + OSDeclareDefaultStructors(IOSDCardEventSource); + + using super = IOEventSource; + + /// + /// Check whether an action needs to perform to handle the card event + /// + /// @return `false` always. The owner must explictly invoke `IOSDCardEventSource::enable()` to trigger another action. + /// + bool checkForWork() override; + +public: + /// + /// Create a card event source with the given action + /// + /// @param owner Owner of the returned instance; the first parameter of the action routine + /// @param action A non-null action to process the card insertion or removal event + /// @return A non-null event source on success, `nullptr` otherwise. + /// + static IOSDCardEventSource* createWithAction(OSObject* owner, Action action); +}; + +#endif /* IOSDCardEventSource_hpp */ diff --git a/RealtekPCIeCardReader/IOSDComplexBlockRequest.cpp b/RealtekPCIeCardReader/IOSDComplexBlockRequest.cpp new file mode 100644 index 0000000..99bf6d3 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDComplexBlockRequest.cpp @@ -0,0 +1,171 @@ +// +// IOSDComplexBlockRequest.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/16/21. +// + +#include "IOSDComplexBlockRequest.hpp" +#include +#include "IOSDHostDriver.hpp" +#include "Utilities.hpp" +#include "Debug.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(IOSDComplexBlockRequest, IOSDSimpleBlockRequest); + +/// +/// Initialize a block request +/// +/// @param driver A non-null host driver that processes the request +/// @param processor A non-null routine to process the request +/// @param buffer The data transfer buffer +/// @param block The starting block number +/// @param nblocks The number of blocks to transfer +/// @param attributes Attributes of the data transfer +/// @param completion The completion routine to call once the data transfer completes +/// @note This function retains the given host driver. +/// The caller must invoke `IOSDBlockRequest::deinit()` before returning the request to the pool. +/// +void IOSDComplexBlockRequest::init(IOSDHostDriver* driver, + Processor processor, + IOMemoryDescriptor* buffer, + UInt64 block, UInt64 nblocks, + IOStorageAttributes* attributes, + IOStorageCompletion* completion) +{ + super::init(driver, processor, buffer, block, nblocks, attributes, completion); + + this->fullBuffer = buffer; + + this->cblock = block; + + this->cnblocks = 0; +} + +/// +/// Deinitialize a block request +/// +/// @note The caller must invoke this function to balance the call of `IOSDBlockRequest::init()`. +/// +void IOSDComplexBlockRequest::deinit() +{ + this->fullBuffer = nullptr; + + this->cblock = 0; + + this->cnblocks = 0; + + super::deinit(); +} + +/// +/// Service the block request +/// +/// @note This function is invoked by the processor work loop to fully service the request. +/// +void IOSDComplexBlockRequest::service() +{ + // The service status + IOReturn retVal = kIOReturnSuccess; + + // The original completion action + IOStorageCompletionAction action = this->completion.action; + + // A buffer that describes a portion of data to be transfered in the current DMA transaction + IOSubMemoryDescriptor* buffer = OSTypeAlloc(IOSubMemoryDescriptor); + + if (buffer == nullptr) + { + retVal = kIOReturnNoMemory; + + goto out; + } + + this->buffer = buffer; + + // `IOStorage::complete()` becomes a noop in each intermediate transaction + this->completion.action = nullptr; + + pinfo("BREQ: Servicing the complex request: Start index = %llu; Number of blocks = %llu.", this->block, this->nblocks); + + // Divide the original request into multiple transactions + while (this->cblock < this->block + this->nblocks) + { + // Calculate the number of blocks to be transfered + this->cnblocks = min(1024ULL, this->block + this->nblocks - this->cblock); + + pinfo("BREQ: Servicing the intermediate transaction: Current start index = %llu; Number of blocks = %llu.", this->cblock, this->cnblocks); + + // Specify the portion of data to be transfered + if (!buffer->initSubRange(this->fullBuffer, (this->cblock - this->block) * 512, this->cnblocks * 512, this->fullBuffer->getDirection())) + { + perr("Failed to initialize the sub-memory descriptor."); + + retVal = kIOReturnError; + + break; + } + + // Guard: Prepare the intermediate request + retVal = this->prepare(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to prepare the request. Error = 0x%x.", retVal); + + break; + } + + // Guard: Process the intermediate request + passert(this->processor != nullptr, "The processor should not be null."); + + retVal = (*this->processor)(this->driver, this); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to process the request. Error = 0x%x.", retVal); + + break; + } + + // Guard: Complete the intermediate request + pinfo("BREQ: Serviced the intermediate transaction: Current start index = %llu; Number of blocks = %llu.", this->cblock, this->cnblocks); + + this->complete(retVal); + + // The intermediate request completes without errors + this->cblock += 1024; + } + +out: + // Restore the original completion action + this->completion.action = action; + + OSSafeReleaseNULL(buffer); + + this->complete(retVal); +} + +/// +/// Get the index of the start block to service the request +/// +/// @note This function is invoked by the processor routine to service the request either fully or partially. +/// +UInt64 IOSDComplexBlockRequest::getBlockOffset() +{ + return this->cblock; +} + +/// +/// Get the number of blocks to service to service the request +/// +/// @note This function is invoked by the processor routine to service the request either fully or partially. +/// +UInt64 IOSDComplexBlockRequest::getNumBlocks() +{ + return this->cnblocks; +} diff --git a/RealtekPCIeCardReader/IOSDComplexBlockRequest.hpp b/RealtekPCIeCardReader/IOSDComplexBlockRequest.hpp new file mode 100644 index 0000000..a3b17e7 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDComplexBlockRequest.hpp @@ -0,0 +1,85 @@ +// +// IOSDComplexBlockRequest.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/16/21. +// + +#ifndef IOSDComplexBlockRequest_hpp +#define IOSDComplexBlockRequest_hpp + +#include "IOSDSimpleBlockRequest.hpp" + +/// +/// Represents a complex request to access blocks on a SD card +/// +/// @note The total number of blocks exceeds the hardware limit, +/// so the driver must service the request in multiple DMA transactions. +/// +class IOSDComplexBlockRequest: public IOSDSimpleBlockRequest +{ + /// Constructors & Destructors + OSDeclareDefaultStructors(IOSDComplexBlockRequest); + + using super = IOSDSimpleBlockRequest; + + /// The original buffer that contains all data to be transfered + IOMemoryDescriptor* fullBuffer; + + /// The current starting block number + UInt64 cblock; + + /// The current number of blocks to transfer + UInt64 cnblocks; + +public: + /// + /// Initialize a block request + /// + /// @param driver A non-null host driver that processes the request + /// @param processor A non-null routine to process the request + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblocks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @note This function retains the given host driver. + /// The caller must invoke `IOSDBlockRequest::deinit()` before returning the request to the pool. + /// + void init(IOSDHostDriver* driver, + Processor processor, + IOMemoryDescriptor* buffer, + UInt64 block, UInt64 nblocks, + IOStorageAttributes* attributes, + IOStorageCompletion* completion) override; + + /// + /// Deinitialize a block request + /// + /// @note The caller must invoke this function to balance the call of `IOSDBlockRequest::init()`. + /// + void deinit() override; + + /// + /// Service the block request + /// + /// @note This function is invoked by the processor work loop to fully service the request. + /// + void service() override; + + /// + /// Get the index of the start block to service the request + /// + /// @note This function is invoked by the processor routine to service the request either fully or partially. + /// + UInt64 getBlockOffset() override; + + /// + /// Get the number of blocks to service to service the request + /// + /// @note This function is invoked by the processor routine to service the request either fully or partially. + /// + UInt64 getNumBlocks() override; +}; + +#endif /* IOSDComplexBlockRequest_hpp */ diff --git a/RealtekPCIeCardReader/IOSDHostDevice.cpp b/RealtekPCIeCardReader/IOSDHostDevice.cpp new file mode 100644 index 0000000..c1af329 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDHostDevice.cpp @@ -0,0 +1,71 @@ +// +// IOSDHostDevice.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/5/21. +// + +#include "IOSDHostDevice.hpp" +#include "IOSDHostDriver.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndAbstractStructors(IOSDHostDevice, IOService); + +// +// MARK: - Card Events Callbacks +// + +/// +/// [UPCALL] Notify the host device when a SD card is inserted +/// +/// @note This callback function runs in a gated context provided by the underlying card reader controller. +/// The host device should implement this function without any blocking operations. +/// A default implementation that notifies the host driver is provided. +/// +void IOSDHostDevice::onSDCardInsertedGated() +{ + this->driver->onSDCardInsertedGated(); + + this->setProperty("Card Present", true); +} + +/// +/// [UPCALL] Notify the host device when a SD card is removed +/// +/// @note This callback function runs in a gated context provided by the underlying card reader controller. +/// The host device should implement this function without any blocking operations. +/// A default implementation that notifies the host driver is provided. +/// +void IOSDHostDevice::onSDCardRemovedGated() +{ + this->driver->onSDCardRemovedGated(); + + this->setProperty("Card Present", false); + + this->removeProperty("Card Characteristics"); +} + +// +// MARK: - IOService Implementation +// + +/// +/// Initialize the host device +/// +/// @param dictionary A nullable matching dictionary +/// @return `true` on success, `false` otherwise. +/// +bool IOSDHostDevice::init(OSDictionary* dictionary) +{ + if (!super::init(dictionary)) + { + return false; + } + + this->setProperty("Card Present", false); + + return true; +} diff --git a/RealtekPCIeCardReader/IOSDHostDevice.hpp b/RealtekPCIeCardReader/IOSDHostDevice.hpp new file mode 100644 index 0000000..17ef042 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDHostDevice.hpp @@ -0,0 +1,386 @@ +// +// IOSDHostDevice.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/5/21. +// + +#ifndef IOSDHostDevice_hpp +#define IOSDHostDevice_hpp + +#include "IOSDBusConfig.hpp" +#include "ClosedRange.hpp" +#include "BitOptions.hpp" +#include "RealtekSDRequest.hpp" // FIXME: SHOULD BE GENERIC + +// TODO: Convert to C++ + +/// +/// Enumerates all host device capabilities +/// +/// @note Not all capabilities are supported by the macOS driver. +/// @note Not all capabilities are applicable to a SD card dreader. +/// @ref /include/linux/mmc/host.h +/// +enum Capability +{ + // ---------- + // | Caps 1 | + // ---------- + + /// Can the host do 4 bit transfers + /// @ref MMC_CAP_4_BIT_DATA + k4BitData = 1 << 0, + + /// Can do MMC high-speed timing + /// @ref MMC_CAP_MMC_HIGHSPEED + kMMCHighSpeec = 1 << 1, + + + +}; + +#define MMC_CAP_MMC_HIGHSPEED (1 << 1) /* */ +#define MMC_CAP_SD_HIGHSPEED (1 << 2) /* Can do SD high-speed timing */ +#define MMC_CAP_SDIO_IRQ (1 << 3) /* Can signal pending SDIO IRQs */ +#define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */ +#define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */ +#define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */ +#define MMC_CAP_AGGRESSIVE_PM (1 << 7) /* Suspend (e)MMC/SD at idle */ +#define MMC_CAP_NONREMOVABLE (1 << 8) /* Nonremovable e.g. eMMC */ +#define MMC_CAP_WAIT_WHILE_BUSY (1 << 9) /* Waits while card is busy */ +#define MMC_CAP_3_3V_DDR (1 << 11) /* Host supports eMMC DDR 3.3V */ +#define MMC_CAP_1_8V_DDR (1 << 12) /* Host supports eMMC DDR 1.8V */ +#define MMC_CAP_1_2V_DDR (1 << 13) /* Host supports eMMC DDR 1.2V */ +#define MMC_CAP_DDR (MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR | \ + MMC_CAP_1_2V_DDR) +#define MMC_CAP_POWER_OFF_CARD (1 << 14) /* Can power off after boot */ +#define MMC_CAP_BUS_WIDTH_TEST (1 << 15) /* CMD14/CMD19 bus width ok */ +#define MMC_CAP_UHS_SDR12 (1 << 16) /* Host supports UHS SDR12 mode */ +#define MMC_CAP_UHS_SDR25 (1 << 17) /* Host supports UHS SDR25 mode */ +#define MMC_CAP_UHS_SDR50 (1 << 18) /* Host supports UHS SDR50 mode */ +#define MMC_CAP_UHS_SDR104 (1 << 19) /* Host supports UHS SDR104 mode */ +#define MMC_CAP_UHS_DDR50 (1 << 20) /* Host supports UHS DDR50 mode */ +#define MMC_CAP_UHS (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | \ + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | \ + MMC_CAP_UHS_DDR50) +#define MMC_CAP_SYNC_RUNTIME_PM (1 << 21) /* Synced runtime PM suspends. */ +#define MMC_CAP_NEED_RSP_BUSY (1 << 22) /* Commands with R1B can't use R1. */ +#define MMC_CAP_DRIVER_TYPE_A (1 << 23) /* Host supports Driver Type A */ +#define MMC_CAP_DRIVER_TYPE_C (1 << 24) /* Host supports Driver Type C */ +#define MMC_CAP_DRIVER_TYPE_D (1 << 25) /* Host supports Driver Type D */ +#define MMC_CAP_DONE_COMPLETE (1 << 27) /* RW reqs can be completed within mmc_request_done() */ +#define MMC_CAP_CD_WAKE (1 << 28) /* Enable card detect wake */ +#define MMC_CAP_CMD_DURING_TFR (1 << 29) /* Commands during data transfer */ +#define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */ +#define MMC_CAP_HW_RESET (1 << 31) /* Reset the eMMC card via RST_n */ + + //u32 caps2; /* More host capabilities */ + +#define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */ +#define MMC_CAP2_FULL_PWR_CYCLE (1 << 2) /* Can do full power cycle */ +#define MMC_CAP2_FULL_PWR_CYCLE_IN_SUSPEND (1 << 3) /* Can do full power cycle in suspend */ +#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */ +#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */ +#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \ + MMC_CAP2_HS200_1_2V_SDR) +#define MMC_CAP2_SD_EXP (1 << 7) /* SD express via PCIe */ +#define MMC_CAP2_SD_EXP_1_2V (1 << 8) /* SD express 1.2V */ +#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */ +#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */ +#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) /* Don't power up before scan */ +#define MMC_CAP2_HS400_1_8V (1 << 15) /* Can support HS400 1.8V */ +#define MMC_CAP2_HS400_1_2V (1 << 16) /* Can support HS400 1.2V */ +#define MMC_CAP2_HS400 (MMC_CAP2_HS400_1_8V | \ + MMC_CAP2_HS400_1_2V) +#define MMC_CAP2_HSX00_1_8V (MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS400_1_8V) +#define MMC_CAP2_HSX00_1_2V (MMC_CAP2_HS200_1_2V_SDR | MMC_CAP2_HS400_1_2V) +#define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17) +#define MMC_CAP2_NO_WRITE_PROTECT (1 << 18) /* No physical write protect pin, assume that card is always read-write */ +#define MMC_CAP2_NO_SDIO (1 << 19) /* Do not send SDIO commands during initialization */ +#define MMC_CAP2_HS400_ES (1 << 20) /* Host supports enhanced strobe */ +#define MMC_CAP2_NO_SD (1 << 21) /* Do not send SD commands during initialization */ +#define MMC_CAP2_NO_MMC (1 << 22) /* Do not send (e)MMC commands during initialization */ +#define MMC_CAP2_CQE (1 << 23) /* Has eMMC command queue engine */ +#define MMC_CAP2_CQE_DCMD (1 << 24) /* CQE can issue a direct command */ +#define MMC_CAP2_AVOID_3_3V (1 << 25) /* Host must negotiate down from 3.3V */ +#define MMC_CAP2_MERGE_CAPABLE (1 << 26) /* Host can merge a segment over the segment size */ + +/// Forward declaration (Client of the SD host device) +class IOSDHostDriver; + +/// +/// Represents an abstract SD host device independent of the underlying card reader controller +/// +class IOSDHostDevice: public IOService +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareAbstractStructors(IOSDHostDevice); + + using super = IOService; + + // + // MARK: - Public Data Structures + // + +public: + /// The maximum current supported by the host device at different signaling voltage levels + struct MaxCurrents + { + /// The maximum current in mA (1.8V) + UInt32 v18; + + /// The maximum current in mA (3.0V) + UInt32 v30; + + /// The maximum current in mA (3.3V) + UInt32 v33; + }; + + // + // MARK: - Host Properties + // + +protected: + /// The host driver (client) + IOSDHostDriver* driver; + + /// The host bus configuration + IOSDBusConfig busConfig; + + /// The maximum currents supported by the host at different signaling voltage levels + MaxCurrents maxCurrents; + + /// The range of clock frequencies (in Hz) supported by the host + ClosedRange clockRange; + + /// The initial clock frequency (in Hz) + UInt32 initialClock; + + /// The range of voltage levels supported by the host (as an OCR value) + UInt32 supportedVoltageRanges; + + /// Host device capabilities + BitOptions caps1; + + /// Host device capabilities + BitOptions caps2; + +public: + // + // MARK: - SD Request Processors + // + + /// + /// Preprocess the given SD command request + /// + /// @param request A SD command request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_pre_req()` defined in `rtsx_pci_sdmmc.c`. + /// + virtual IOReturn preprocessRequest(RealtekSDRequest& request) = 0; + + /// + /// Process the given SD command request + /// + /// @param request A SD command request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_request()` defined in `rtsx_pci_sdmmc.c`. + /// + virtual IOReturn processRequest(RealtekSDRequest& request) = 0; + + /// + /// Postprocess the given SD command request + /// + /// @param request A SD command request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_post_req()` defined in `rtsx_pci_sdmmc.c`. + /// + virtual IOReturn postprocessRequest(RealtekSDRequest& request) = 0; + + // + // MARK: - SD Bus Configurator + // + + /// + /// Apply the given I/O configuration + /// + /// @param config An I/O config + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_set_ios()` defined in `rtsx_pci_sdmmc.c`. + /// + virtual IOReturn setBusConfig(const IOSDBusConfig& config) = 0; + + /// + /// Switch the signal voltage + /// + /// @param config An I/O config that contains the target signal voltage level + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replace `sdmmc_switch_voltage()` defined in `rtsx_pci_sdmmc.c`. + /// + virtual IOReturn switchSignalVoltage(const IOSDBusConfig& config) = 0; + + // + // MARK: - Tuning + // + + /// + /// Execute the tuning algorithm + /// + /// @param config An I/O config that contains the current I/O settings + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_execute_tuning()` defined in `rtsx_pci_sdmmc.c`. + /// + virtual IOReturn executeTuning(const IOSDBusConfig& config) = 0; + + // + // MARK: - Card Status + // + + /// + /// Check whether the card has write protection enabled + /// + /// @param result Set `true` if the card is write protected, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_get_ro()` defined in `rtsx_pci_sdmmc.c`. + /// + virtual IOReturn isCardWriteProtected(bool& result) = 0; + + /// + /// Check whether the card exists + /// + /// @param result Set `true` if the card exists, `false` otherwise. + /// @return `kIOReturnSuccess` always. + /// @note Port: This function replaces `sdmmc_get_cd()` defined in `rtsx_pci_sdmmc.c`. + /// + virtual IOReturn isCardPresent(bool& result) = 0; + + /// + /// Check whether the command line of the card is busy + /// + /// @param result Set `true` if the card CMD line is high, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + virtual IOReturn isCardCommandLineBusy(bool& result) = 0; + + /// + /// Check whether the data line of the card is busy + /// + /// @param result Set `true` if the card DAT lines are high, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + virtual IOReturn isCardDataLineBusy(bool& result) = 0; + + // + // MARK: - Host Capabilities + // + + /// + /// Get the current host bus configuration + /// + /// @return A reference to the host bus configuration. + /// + inline IOSDBusConfig& getHostBusConfig() + { + return this->busConfig; + } + + /// + /// Get the maximum currents supported by the host at different signaling voltage levels + /// + /// @return The host maximum currents. + /// + inline MaxCurrents getHostMaxCurrents() + { + return this->maxCurrents; + } + + /// + /// Get the range of clock frequencies supported by the host + /// + /// @return The host clock range. + /// + inline ClosedRange getHostClockRange() + { + return this->clockRange; + } + + /// + /// Get the initial clock frequency + /// + /// @return The host initial clock frequency. + /// + inline UInt32 getHostInitialClock() + { + return this->initialClock; + } + + /// + /// Set the initial clock frequency + /// + /// @param clock The host initial clock frequency + /// + inline void setHostInitialClock(UInt32 clock) + { + this->initialClock = clock; + } + + /// + /// Get the host supported ranges of voltage levels + /// + /// @return The voltage range as an OCR value. + /// + inline UInt32 getHostSupportedVoltageRanges() + { + return this->supportedVoltageRanges; + } + + inline BitOptions getCaps1() + { + return this->caps1; + } + + inline BitOptions getCaps2() + { + return this->caps2; + } + + // + // MARK: - Card Events Callbacks + // + + /// + /// [UPCALL] Notify the host device when a SD card is inserted + /// + /// @note This callback function runs in a gated context provided by the underlying card reader controller. + /// The host device should implement this function without any blocking operations. + /// A default implementation that notifies the host driver is provided. + /// + virtual void onSDCardInsertedGated(); + + /// + /// [UPCALL] Notify the host device when a SD card is removed + /// + /// @note This callback function runs in a gated context provided by the underlying card reader controller. + /// The host device should implement this function without any blocking operations. + /// A default implementation that notifies the host driver is provided. + /// + virtual void onSDCardRemovedGated(); + + // + // MARK: - IOService Implementation + // + + /// + /// Initialize the host device + /// + /// @param dictionary A nullable matching dictionary + /// @return `true` on success, `false` otherwise. + /// + bool init(OSDictionary* dictionary = nullptr) override; +}; + +#endif /* IOSDHostDevice_hpp */ diff --git a/RealtekPCIeCardReader/IOSDHostDriver.cpp b/RealtekPCIeCardReader/IOSDHostDriver.cpp new file mode 100644 index 0000000..b97211c --- /dev/null +++ b/RealtekPCIeCardReader/IOSDHostDriver.cpp @@ -0,0 +1,2810 @@ +// +// IOSDHostDriver.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/1/21. +// + +#include "IOSDHostDriver.hpp" +#include "BitOptions.hpp" +#include "Debug.hpp" +#include "IOSDBlockStorageDevice.hpp" +#include +#include +#include + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(IOSDHostDriver, IOService); + +// +// MARK: - I/O Requests +// + +/// +/// Submit the given request to the queue +/// +/// @param processor The processor that services the request +/// @param buffer The data transfer buffer +/// @param block The starting block number +/// @param nblocks The number of blocks to transfer +/// @param attributes Attributes of the data transfer +/// @param completion The completion routine to call once the data transfer completes +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn IOSDHostDriver::submitBlockRequest(IOSDBlockRequest::Processor processor, IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion) +{ + pinfo("BREQ: Start Block Index = %llu; Number of Blocks = %llu; Number of Bytes = %llu.", block, nblocks, nblocks * 512); + + psoftassert(buffer->getLength() == nblocks * 512, "Buffer lengths mismatched."); + + // Guard: Ensure that the queue event source is still enabled + // It is enabled if and only if the card is not ejected or removed. + if (!this->queueEventSource->isEnabled()) + { + perr("The queue event source is disabled."); + + return kIOReturnNoMedia; + } + + // Guard: The maximum number of blocks in one request is 1024 + // Split the incoming request into multiple smaller one if necessary + IOSDBlockRequest* request = nullptr; + + if (nblocks <= 1024) + { + request = this->allocateSimpleBlockRequestFromPool(); + } + else + { + request = this->allocateComplexBlockRequestFromPool(); + } + + passert(request != nullptr, "The block request should not be null at this moment."); + + request->init(this, processor, buffer, block, nblocks, attributes, completion); + + this->pendingRequests->enqueueRequest(request); + + this->queueEventSource->enable(); + + return kIOReturnSuccess; +} + +/// +/// Submit a request to read a single block +/// +/// @param buffer The data transfer buffer +/// @param block The starting block number +/// @param nblocks The number of blocks to transfer +/// @param attributes Attributes of the data transfer +/// @param completion The completion routine to call once the data transfer completes +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The given arguments are guaranteed to be valid. +/// +IOReturn IOSDHostDriver::submitReadBlockRequest(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion) +{ + auto processor = OSMemberFunctionCast(IOSDBlockRequest::Processor, this, &IOSDHostDriver::processReadBlockRequest); + + return this->submitBlockRequest(processor, buffer, block, nblocks, attributes, completion); +} + +/// +/// Submit a request to write a single block +/// +/// @param buffer The data transfer buffer +/// @param block The starting block number +/// @param nblocks The number of blocks to transfer +/// @param attributes Attributes of the data transfer +/// @param completion The completion routine to call once the data transfer completes +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The given arguments are guaranteed to be valid. +/// +IOReturn IOSDHostDriver::submitWriteBlockRequest(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion) +{ + auto processor = OSMemberFunctionCast(IOSDBlockRequest::Processor, this, &IOSDHostDriver::processWriteBlockRequest); + + return this->submitBlockRequest(processor, buffer, block, nblocks, attributes, completion); +} + +/// +/// Submit a request to read multiple blocks +/// +/// @param buffer The data transfer buffer +/// @param block The starting block number +/// @param nblocks The number of blocks to transfer +/// @param attributes Attributes of the data transfer +/// @param completion The completion routine to call once the data transfer completes +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The given arguments are guaranteed to be valid. +/// +IOReturn IOSDHostDriver::submitReadBlocksRequest(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion) +{ + auto processor = OSMemberFunctionCast(IOSDBlockRequest::Processor, this, &IOSDHostDriver::processReadBlocksRequest); + + return this->submitBlockRequest(processor, buffer, block, nblocks, attributes, completion); +} + +/// +/// Submit a request to write multiple blocks +/// +/// @param buffer The data transfer buffer +/// @param block The starting block number +/// @param nblocks The number of blocks to transfer +/// @param attributes Attributes of the data transfer +/// @param completion The completion routine to call once the data transfer completes +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The given arguments are guaranteed to be valid. +/// +IOReturn IOSDHostDriver::submitWriteBlocksRequest(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion) +{ + auto processor = OSMemberFunctionCast(IOSDBlockRequest::Processor, this, &IOSDHostDriver::processWriteBlocksRequest); + + return this->submitBlockRequest(processor, buffer, block, nblocks, attributes, completion); +} + +/// +/// Process the given request to read a single block +/// +/// @param request A non-null block request +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The return value will be passed to the storage completion routine. +/// @note When this function is invoked, the DMA command is guaranteed to be non-null and prepared. +/// +IOReturn IOSDHostDriver::processReadBlockRequest(IOSDBlockRequest* request) +{ + pinfo("Processing the request that reads a single block..."); + + passert(request->getBlockOffset() <= UINT32_MAX, "The maximum capacity supported is 2TB."); + + auto creq = RealtekSDRequestFactory::CMD17(static_cast(request->getBlockOffset()), request->getDMACommand()); + + return this->waitForRequest(creq); +} + +/// +/// Process the given request to read multiple blocks +/// +/// @param request A non-null block request +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The return value will be passed to the storage completion routine. +/// @note When this function is invoked, the DMA command is guaranteed to be non-null and prepared. +/// +IOReturn IOSDHostDriver::processReadBlocksRequest(IOSDBlockRequest* request) +{ + pinfo("Processing the request that reads multiple blocks..."); + + passert(request->getBlockOffset() <= UINT32_MAX, "The maximum capacity supported is 2TB."); + + auto creq = RealtekSDRequestFactory::CMD18(static_cast(request->getBlockOffset()), request->getDMACommand(), request->getNumBlocks()); + + return this->waitForRequest(creq); +} + +/// +/// Process the given request to write a single block +/// +/// @param request A non-null block request +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The return value will be passed to the storage completion routine. +/// @note When this function is invoked, the DMA command is guaranteed to be non-null and prepared. +/// +IOReturn IOSDHostDriver::processWriteBlockRequest(IOSDBlockRequest* request) +{ + pinfo("Processing the request that writes a single block..."); + + passert(request->getBlockOffset() <= UINT32_MAX, "The maximum capacity supported is 2TB."); + + auto creq = RealtekSDRequestFactory::CMD24(static_cast(request->getBlockOffset()), request->getDMACommand()); + + return this->waitForRequest(creq); +} + +/// +/// Process the given request to write multiple blocks +/// +/// @param request A non-null block request +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The return value will be passed to the storage completion routine. +/// @note When this function is invoked, the DMA command is guaranteed to be non-null and prepared. +/// +IOReturn IOSDHostDriver::processWriteBlocksRequest(IOSDBlockRequest* request) +{ + // Issue the ACMD23 to set the number of pre-erased blocks + pinfo("Issuing an ACMD23 to set the number of pre-erased blocks..."); + + passert(request->getNumBlocks() <= ((1 << 23) - 1), "The number of blocks should be less than 2^23 - 1."); + + auto preq = RealtekSDRequestFactory::ACMD23(static_cast(request->getNumBlocks())); + + IOReturn retVal = this->waitForAppRequest(preq, this->card->getRCA()); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the ACMD23 to set the number of pre-erased blocks. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The ACMD23 has been issued."); + + // Process the block request + pinfo("Processing the request that writes multiple blocks..."); + + passert(request->getBlockOffset() <= UINT32_MAX, "The maximum capacity supported is 2TB."); + + auto creq = RealtekSDRequestFactory::CMD25(static_cast(request->getBlockOffset()), request->getDMACommand(), request->getNumBlocks()); + + return this->waitForRequest(creq); +} + +/// +/// Finalize a request that has been processed +/// +/// @param request A non-null block request +/// @note This function is the completion routine registerd with the block request event source. +/// It deinitializes the given request and puts it back to the block request pool. +/// +void IOSDHostDriver::finalizeBlockRequest(IOSDBlockRequest* request) +{ + pinfo("The given request has been processed."); + + request->deinit(); + + this->releaseBlockRequestToPool(request); +} + +// +// MARK: - Query Host Properties +// + +/// +/// Check whether the host supports the High Speed mode +/// +/// @return `true` if the High Speed mode is supported by the host. +/// +bool IOSDHostDriver::hostSupportsHighSpeedMode() +{ + return this->host->getCaps1().contains(MMC_CAP_SD_HIGHSPEED); +} + +/// +/// Check whether the host supports the Ultra High Speed mode +/// +/// @return `true` if the Ultra High Speed mode is supported by the host. +/// +bool IOSDHostDriver::hostSupportsUltraHighSpeedMode() +{ + auto caps = this->host->getCaps1(); + + bool s4b = caps.contains(Capability::k4BitData); + + bool uhs = caps.contains(MMC_CAP_UHS_SDR12) || + caps.contains(MMC_CAP_UHS_SDR25) || + caps.contains(MMC_CAP_UHS_SDR50) || + caps.contains(MMC_CAP_UHS_SDR104) || + caps.contains(MMC_CAP_UHS_DDR50); + + return s4b && uhs; +} + +/// +/// Check whether the host supports the 4-bit bus width +/// +/// @return `true` if the 4-bit bus width is supported by the host. +/// +bool IOSDHostDriver::hostSupports4BitBusWidth() +{ + return this->host->getCaps1().contains(Capability::k4BitData); +} + +/// +/// Get the maximum current setting at its current voltage +/// +/// @return The maximum current in mA. +/// @note Port: This function replaces `sd_get_host_max_current()` defined in `sd.c`. +/// +UInt32 IOSDHostDriver::getHostMaxCurrent() +{ + IOSDHostDevice::MaxCurrents maxCurrents = this->host->getHostMaxCurrents(); + + const IOSDBusConfig& config = this->host->getHostBusConfig(); + + UInt32 maxCurrent = 0; + + switch (1 << config.vdd) + { + case MMC_VDD_165_195: + { + maxCurrent = maxCurrents.v18; + + break; + } + + case MMC_VDD_29_30: + case MMC_VDD_30_31: + { + maxCurrent = maxCurrents.v30; + + break; + } + + case MMC_VDD_32_33: + case MMC_VDD_33_34: + { + maxCurrent = maxCurrents.v33; + + break; + } + default: + { + perr("Unsupported host signal voltage level."); + + break; + } + } + + return maxCurrent; +} + +/// +/// Select voltage levels mutually supported by the host and the card +/// +/// @param ocr The card OCR value +/// @return The OCR value that contains voltage levels supported by both parties. +/// @note Port: This function replaces `mmc_select_voltage()` defined in `core.c`. +/// +UInt32 IOSDHostDriver::selectMutualVoltageLevels(UInt32 ocr) +{ + pinfo("Selecting voltage levels that are supported by both sides..."); + + // Sanitize the card OCR value + // The low 7 bits should be zero + if ((ocr & 0x7F) != 0) + { + pwarning("The card OCR value 0x%08x reports to support undefined voltage levels.", ocr); + + ocr &= ~0x7F; + } + + pinfo("[OCR] Card = 0x%08x.", ocr); + + // Filter out voltage levels unsupported by both sides + ocr &= this->host->getHostSupportedVoltageRanges(); + + pinfo("[OCR] Both = 0x%08x.", ocr); + + if (ocr == 0) + { + pwarning("No voltage levels supported by the host and the card."); + + return 0; + } + + // TODO: CHANGE THIS TO GETTER FUNC? + if (this->host->getCaps2().contains(MMC_CAP2_FULL_PWR_CYCLE)) + { + pinfo("The host device supports a full power cycle."); + + ocr &= 3 << (ffs(ocr) - 1); + + pinfo("Restarting the host device with the new OCR value 0x%08x.", ocr); + + psoftassert(this->powerCycle(ocr) == kIOReturnSuccess, + "Failed to restart the power of the card with the new OCR value 0x%08x.", ocr); + } + else + { + ocr &= 3 << (myfls(ocr) - 1); + + psoftassert(myfls(ocr) - 1 == this->host->getHostBusConfig().vdd, + "The host voltage supply exceeds the card's supported value."); + } + + pinfo("Selected mutual voltage levels = 0x%x.", ocr); + + return ocr; +} + +/// +/// Get the host device +/// +/// @return The non-null host device. +/// +IOSDHostDevice* IOSDHostDriver::getHostDevice() +{ + return this->host; +} + +// +// MARK: - Adjust Host Bus Settings +// + +/// +/// [Shared] Set the bus config +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_set_ios()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::setBusConfig() +{ + const IOSDBusConfig& config = this->host->getHostBusConfig(); + + config.print(); + + return this->host->setBusConfig(config); +} + +/// +/// Set the initial bus config +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_set_initial_state()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::setInitialBusConfig() +{ + pinfo("Setting the initial bus config..."); + + IOSDBusConfig& config = this->host->getHostBusConfig(); + + config.chipSelect = IOSDBusConfig::ChipSelect::kDoNotCare; + + config.busMode = IOSDBusConfig::BusMode::kPushPull; + + config.busWidth = IOSDBusConfig::BusWidth::k1Bit; + + config.busTiming = IOSDBusConfig::BusTiming::kLegacy; + + config.driverType = IOSDBusConfig::DriverType::kTypeB; + + return this->setBusConfig(); +} + +/// +/// Set the SPI chip select mode +/// +/// @param chipSelect The target mode +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_set_chip_select()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::setChipSelect(IOSDBusConfig::ChipSelect chipSelect) +{ + // TODO: Optimize this: Realtek's hardware seems to put the chip select to High by default +// IOSDBusConfig& config = this->host->getHostBusConfig(); +// +// config.chipSelect = chipSelect; +// +// return this->setBusConfig(); + + pinfo("Optimization: Realtek does not support the chip select."); + + return kIOReturnSuccess; +} + +/// +/// Set the bus speed mode +/// +/// @param timing The target bus speed mode +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_set_timing()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::setBusTiming(IOSDBusConfig::BusTiming timing) +{ + IOSDBusConfig& config = this->host->getHostBusConfig(); + + config.busTiming = timing; + + return this->setBusConfig(); +} + +/// +/// Set the bus clock +/// +/// @param clock The target clock frequency in Hz +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_set_clock()` defined in `core.c`. +/// @warning If the given clock frequency is beyond the range of supported clock frequencies, +/// this function will adjust the final clock appropriately. +/// +IOReturn IOSDHostDriver::setBusClock(UInt32 clock) +{ + ClosedRange range = this->host->getHostClockRange(); + + IOSDBusConfig& config = this->host->getHostBusConfig(); + + psoftassert(range.contains(clock), "The given clock %u Hz is beyond the range.", clock); + + config.clock = min(range.upperBound, clock); + + return this->setBusConfig(); +} + +/// +/// Set the bus width +/// +/// @param width The target bus width +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_set_bus_width()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::setBusWidth(IOSDBusConfig::BusWidth width) +{ + IOSDBusConfig& config = this->host->getHostBusConfig(); + + config.busWidth = width; + + return this->setBusConfig(); +} + +/// +/// Set the signal voltage +/// +/// @param voltage The target signal voltage +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_set_signal_voltage()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::setSignalVoltage(IOSDBusConfig::SignalVoltage voltage) +{ + IOSDBusConfig& config = this->host->getHostBusConfig(); + + auto oldVoltage = config.signalVoltage; + + config.signalVoltage = voltage; + + IOReturn retVal = this->host->switchSignalVoltage(config); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to switch to the signal voltage %hhu. Error = 0x%x.", voltage, retVal); + + config.signalVoltage = oldVoltage; + } + + return retVal; +} + +/// +/// Set the initial signal voltage +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_set_initial_signal_voltage()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::setInitialSignalVoltage() +{ + // Try to set the signal voltage to 3.3V + pinfo("Attempt to set the signal voltage to 3.3V."); + + if (this->setSignalVoltage(IOSDBusConfig::SignalVoltage::k3d3V) == kIOReturnSuccess) + { + pinfo("The initial signal voltage has been set to 3.3V."); + + return kIOReturnSuccess; + } + + perr("Failed to set the signal voltage to 3.3V. Will try to set the signal voltage to 1.8V."); + + if (this->setSignalVoltage(IOSDBusConfig::SignalVoltage::k1d8V) == kIOReturnSuccess) + { + pinfo("The initial signal voltage has been set to 1.8V."); + + return kIOReturnSuccess; + } + + perr("Failed to set the signal voltage to 1.8V. Will try to set the signal voltage to 1.2V."); + + if (this->setSignalVoltage(IOSDBusConfig::SignalVoltage::k1d2V) == kIOReturnSuccess) + { + pinfo("The initial signal voltage has been set to 1.2V."); + + return kIOReturnSuccess; + } + + perr("Cannot set the initial signal voltage to one of supported values."); + + return kIOReturnError; +} + +/// +/// Set the signal voltage for the Ultra High Speed mode +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_host_set_uhs_voltage()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::setUltraHighSpeedSignalVoltage() +{ + // The clock must be gated for 5ms during a signal voltage level switch + IOSDBusConfig& config = this->host->getHostBusConfig(); + + UInt32 clock = config.clock; + + config.clock = 0; + + IOReturn retVal = this->setBusConfig(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to gate the clock. Error = 0x%x.", retVal); + + return retVal; + } + + // Set the signal voltage to 1.8V + retVal = this->setSignalVoltage(IOSDBusConfig::SignalVoltage::k1d8V); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to switch the signal voltage to 1.8V. Error = 0x%x.", retVal); + + return retVal; + } + + // Keep the clock gated for at least 10ms + IOSleep(10); + + // Restore the clock + config.clock = clock; + + retVal = this->setBusConfig(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to restore the clock. Error = 0x%x.", retVal); + + return retVal; + } + + return kIOReturnSuccess; +} + +/// +/// Execute the tuning algorithm +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_execute_tuning()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::executeTuning() +{ + // TODO: RETUNE ENABLE??? + return this->host->executeTuning(this->host->getHostBusConfig()); +} + +/// +/// Power up the SD stack +/// +/// @param ocr The OCR value from which to select the VDD value +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_power_up()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::powerUp(UInt32 ocr) +{ + // Check whether the bus power is already on + IOSDBusConfig& config = this->host->getHostBusConfig(); + + if (config.powerMode == IOSDBusConfig::PowerMode::kPowerOn) + { + pinfo("The bus power is already on."); + + return kIOReturnSuccess; + } + + // Power up the bus + // Set the initial bus config without the clock running + pinfo("Powering up the bus..."); + + config.vdd = myfls(ocr) - 1; + + config.powerMode = IOSDBusConfig::PowerMode::kPowerUp; + + IOReturn retVal = this->setInitialBusConfig(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the initial bus state. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The bus power is now up."); + + // Set the initial signal voltage level + pinfo("Setting the initial signal voltage..."); + + retVal = this->setInitialSignalVoltage(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the initial signal voltage level. Error = 0x%x.", retVal); + + return retVal; + } + + // Wait for a while until the power supply becomes stable + IOSleep(config.powerDelay); + + pinfo("The initial signal voltage has been set."); + + // Power on the bus with the clock running + pinfo("Powering on the bus..."); + + config.clock = this->host->getHostInitialClock(); + + config.powerMode = IOSDBusConfig::PowerMode::kPowerOn; + + retVal = this->setBusConfig(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to power up the bus with the clock running. Error = 0x%x.", retVal); + } + + // Wait for a while until the voltage level becomes stable + IOSleep(config.powerDelay); + + pinfo("The bus power is now on."); + + return retVal; +} + +/// +/// Power off the SD stack +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_power_up()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::powerOff() +{ + // Check whether the bus power is already off + IOSDBusConfig& config = this->host->getHostBusConfig(); + + if (config.powerMode == IOSDBusConfig::PowerMode::kPowerOff) + { + pinfo("The bus power is already off."); + + return kIOReturnSuccess; + } + + pinfo("Powering off the bus..."); + + config.clock = 0; + + config.vdd = 0; + + config.powerMode = IOSDBusConfig::PowerMode::kPowerOff; + + IOReturn retVal = this->setInitialBusConfig(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to power off the bus. Error = 0x%x.", retVal); + } + + // Some cards require a short delay after powered off before turned on again + IOSleep(1); + + return retVal; +} + +/// +/// Reboot the SD stack +/// +/// @param ocr The OCR value from which to select the VDD value +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_power_cycle()` defined in `core.c`. +/// +IOReturn IOSDHostDriver::powerCycle(UInt32 ocr) +{ + // Power off the stack + IOReturn retVal = this->powerOff(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to power off the stack. Error = 0x%x.", retVal); + + return retVal; + } + + // Wait at least 1 ms according to the SD specification + IOSleep(1); + + // Power on the stack + retVal = this->powerUp(ocr); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to power on the stack. Error = 0x%x.", retVal); + + return retVal; + } + + return kIOReturnSuccess; +} + +// +// MARK: - DMA Utility +// + +/// +/// [Convenient] Allocate a DMA capable buffer +/// +/// @param size The number of bytes +/// @return A non-null IODMACommand instance on success, `nullptr` otherwise. +/// @note The calling thread can be blocked. +/// +IODMACommand* IOSDHostDriver::allocateDMABuffer(IOByteCount size) +{ + // Guard: Allocate a buffer of the given size + IOMemoryDescriptor* descriptor = IOBufferMemoryDescriptor::withCapacity(size, kIODirectionInOut); + + if (descriptor == nullptr) + { + perr("Failed to allocate a %llu-byte buffer.", size); + + return nullptr; + } + + // Guard: Page in and wire down the buffer + if (descriptor->prepare() != kIOReturnSuccess) + { + perr("Failed to page in and wire down the buffer."); + + descriptor->release(); + + return nullptr; + } + + // Get a preallocated DMA command from the pool + // Note that the calling thread will be blocked until a command is available + IODMACommand* command = this->allocateDMACommandFromPool(); + + // Guard: Associate the memory descriptor with the DMA command + // Note that the DMA command is prepared automatically + if (command->setMemoryDescriptor(descriptor) != kIOReturnSuccess) + { + perr("Failed to associate the memory descriptor with the DMA command."); + + this->releaseDMACommandToPool(command); + + descriptor->complete(); + + descriptor->release(); + + return nullptr; + } + + // Note that `IODMACommand::setMemoryDescriptor()` retains the memory descriptor + // We can release it here, so that the reference count becomes 1 again + descriptor->release(); + + return command; +} + +/// +/// [Convenient] Release the given DMA capable buffer +/// +/// @param command A non-null IODMACommand instance previously returned by `IOSDHostDriver::allocateDMABuffer()`. +/// +void IOSDHostDriver::releaseDMABuffer(IODMACommand* command) +{ + // Clear the memory descriptor + // Note that the DMA command is completed automatically + // This method also releases the memory descriptor previously associated with the DMA command + // @see https://opensource.apple.com/source/xnu/xnu-7195.101.1/iokit/Kernel/IODMACommand.cpp.auto.html + // We do not need to `complete` the memory descriptor manually, + // because its free method checks whether it is wired and if so `completes` it. + // @see https://opensource.apple.com/source/xnu/xnu-7195.101.1/iokit/Kernel/IOMemoryDescriptor.cpp.auto.html + psoftassert(command->clearMemoryDescriptor() == kIOReturnSuccess, + "Failed to deassociate the memory descriptor with the given DMA command."); + + // Return the command back to the pool + this->releaseDMACommandToPool(command); +} + +// +// MARK: - SD Request Center +// + +/// +/// [Helper] Send the given SD command request and wait for the response +/// +/// @param request A SD command request +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn IOSDHostDriver::waitForRequest(RealtekSDRequest& request) +{ + // TODO: CHECK THIS & IMP THIS + return this->host->processRequest(request); +} + +/// +/// [Helper] Send the given SD application command request and wait for the response +/// +/// @param request A SD application command request +/// @param rca The card relative address +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_wait_for_app_cmd()` defined in `sd_ops.c`. +/// @note This function issues a CMD55 before sending the given request. +/// +IOReturn IOSDHostDriver::waitForAppRequest(RealtekSDRequest& request, UInt32 rca) +{ + // Guard: Send the CMD55 + IOReturn retVal = this->CMD55(rca); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue a CMD55. Error = 0x%x.", retVal); + + return retVal; + } + + // Send the application command + return this->waitForRequest(request); +} + +/// +/// CMD0: Reset all cards to the idle state +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_go_idle()` defined in `mmc_ops.c`. +/// +IOReturn IOSDHostDriver::CMD0() +{ + pinfo("Sending CMD0..."); + + // Guard: Ensure that chip select is high to prevent the chip entering into the SPI mode + pinfo("Setting the chip select to high to prevent the SPI mode..."); + + IOReturn retVal = this->setChipSelect(IOSDBusConfig::ChipSelect::kHigh); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the chip select to high. Error = 0x%x.", retVal); + + return retVal; + } + + IOSleep(1); + + pinfo("The chip select is now set to high."); + + // Issue the CMD0 + pinfo("Sending CMD0 to the card..."); + + auto request = RealtekSDRequestFactory::CMD0(); + + retVal = this->waitForRequest(request); + + IOSleep(1); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the CMD0. Error = 0x%x.", retVal); + + psoftassert(this->setChipSelect(IOSDBusConfig::ChipSelect::kDoNotCare) == kIOReturnSuccess, "Failed to reset the chip select."); + + return retVal; + } + + pinfo("The card is now in the idle state."); + + // Reset the chip select value + pinfo("Setting the chip select to do-not-care..."); + + retVal = this->setChipSelect(IOSDBusConfig::ChipSelect::kDoNotCare); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to reset the chip select. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("CMD0 has been sent."); + + return kIOReturnSuccess; +} + +/// +/// CMD2: Ask any card to send the card identification data +/// +/// @param buffer A non-null that stores the raw card identification data on return +/// @param length Specify the number of bytes read from the card identification data (must not exceed 16 bytes) +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_send_cid()` defined in `mmc_ops.c`. +/// @note Upon a successful return, the given buffer contains the response data as is. +/// The caller is responsible for dealing with the endianness and parsing the data. +/// @note It is recommended to use `IOSDHostDriver::CMD2(cid:)` to fetch and parse the data in one function call. +/// +IOReturn IOSDHostDriver::CMD2(UInt8* buffer, IOByteCount length) +{ + auto request = RealtekSDRequestFactory::CMD2(); + + IOReturn retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the CMD2. Error = 0x%x.", retVal); + + return retVal; + } + + length = min(sizeof(CID), length); + + memcpy(buffer, request.command.reinterpretResponseAs()->value, length); + + return kIOReturnSuccess; +} + +/// +/// CMD2: Ask any card to send the card identification data and parse the returned data +/// +/// @param cid The **parsed** card identification data on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_send_cid()` defined in `mmc_ops.c`. +/// +IOReturn IOSDHostDriver::CMD2(CID& cid) +{ + UInt8 buffer[sizeof(CID)] = {}; + + IOReturn retVal = this->CMD2(buffer); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to fetch the raw card identification data. Error = 0x%x.", retVal); + + return retVal; + } + + // FIXME: Compatibility layer: REMOVE THIS ONCE THE DECODE FUNC IS REWRITTEN + // FIXME: The decode function uses Linux's UNSTUFF_BITS macro to extract bits from the response + // FIXME: This macro assumes that the data is an array of four UInt32 intergers encoded in big endian + UInt32* data = reinterpret_cast(buffer); + + for (auto index = 0; index < sizeof(CID) / sizeof(UInt32); index += 1) + { + data[index] = OSSwapInt32(data[index]); + } + + if (!CID::decode(data, cid)) + { + perr("Failed to decode the raw card identification data."); + + return kIOReturnInvalid; + } + + pinfo("Fetched and decoded the card identification data successfully."); + + return kIOReturnSuccess; +} + +/// +/// CMD3: Ask the card to publish its relative address +/// +/// @param rca The card relative address on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_send_relative_addr()` defined in `sd_ops.c`. +/// +IOReturn IOSDHostDriver::CMD3(UInt32& rca) +{ + auto request = RealtekSDRequestFactory::CMD3(); + + IOReturn retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the CMD3. Error = 0x%x.", retVal); + + return retVal; + } + + rca = request.command.reinterpretResponseAs()->getRCA(); + + return kIOReturnSuccess; +} + +/// +/// CMD6: Check switchable function or switch the card function +/// +/// @param mode Pass 0 to check switchable function or 1 to switch the card function +/// @param group The function group +/// @param value The function value +/// @param response A non-null buffer that stores the response on return +/// @param length Specify the number of bytes read from the response (must not exceed 64 bytes) +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_sd_switch()` defined in `sd_ops.c`. +/// @note This function allocates an internal 64-byte DMA capable buffer to store the response from the card. +/// The response is then copied to the given `response` buffer. +/// @seealso `IOSDHostDriver::CMD6(mode:group:value:response)` if the caller desires to reuse an existing buffer. +/// +IOReturn IOSDHostDriver::CMD6(UInt32 mode, UInt32 group, UInt8 value, UInt8* response, IOByteCount length) +{ + // Sanitize the given `mode` and `value` + pinfo("CMD6: [ORG] Mode = %d; Group = %d; Value = %d.", mode, group, value); + + mode = !!mode; + + value &= 0x0F; + + pinfo("CMD6: [SAN] Mode = %d; Group = %d; Value = %d.", mode, group, value); + + // Allocate a DMA buffer + IODMACommand* dma = this->allocateDMABuffer(64); + + if (dma == nullptr) + { + perr("Failed to allocate a 64-byte DMA buffer."); + + return kIOReturnNoMemory; + } + + // Send the command + // TODO: SET DATA TIMEOUT???? + auto request = RealtekSDRequestFactory::CMD6(mode, group, value, dma); + + IOReturn retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the CMD6. Error = 0x%x.", retVal); + + this->releaseDMABuffer(dma); + + return retVal; + } + + // Copy the SD status from the DMA buffer + length = min(length, 64); + + retVal = dma->readBytes(0, response, length) == length ? kIOReturnSuccess : kIOReturnError; + + this->releaseDMABuffer(dma); + + return retVal; +} + +/// +/// CMD6: Check switchable function or switch the card function +/// +/// @param mode Pass 0 to check switchable function or 1 to switch the card function +/// @param group The function group +/// @param value The function value +/// @param response A non-null and prepared memory descriptor that stores the response on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_sd_switch()` defined in `sd_ops.c`. +/// @note This function uses the given response buffer to initiate a DMA read operation. +/// The caller is responsbile for managing the life cycle of the given buffer. +/// +IOReturn IOSDHostDriver::CMD6(UInt32 mode, UInt32 group, UInt8 value, IOMemoryDescriptor* response) +{ + // Guard: Verify the given response buffer + if (response == nullptr) + { + perr("The given response buffer cannot be NULL."); + + return kIOReturnBadArgument; + } + + // Sanitize the given `mode` and `value` + pinfo("CMD6: [ORG] Mode = %d; Group = %d; Value = %d.", mode, group, value); + + mode = !!mode; + + value &= 0x0F; + + pinfo("CMD6: [SAN] Mode = %d; Group = %d; Value = %d.", mode, group, value); + + // Guard: Associate the given response buffer with the DMA command + // Note that the DMA command is prepared automatically + IODMACommand* dma = this->allocateDMACommandFromPool(); + + IOReturn retVal = dma->setMemoryDescriptor(response); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to associate the given response with the DMA command. Error = 0x%x.", retVal); + + return retVal; + } + + // Generate the SD command request + auto request = RealtekSDRequestFactory::CMD6(mode, group, value, dma); + + // TODO: Set the data timeout as Linux??? + // TODO: Realtek's driver seems to ignore the data timeout in the mmc_data struct + retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the CMD6. Error = 0x%x.", retVal); + } + + // Deassociate the given response buffer with the DMA command + // Note that the DMA command is completed automatically + psoftassert(dma->clearMemoryDescriptor() == kIOReturnSuccess, + "Failed to deassociate the given response with the DMA command."); + + // Return the DMA command back + this->releaseDMACommandToPool(dma); + + return retVal; +} + +/// +/// CMD7: Select a card +/// +/// @param rca The relative address of the card to be selected; +/// Pass 0 to deselect all cards +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `_mmc_select_card()` defined in `sd_ops.c`. +/// +IOReturn IOSDHostDriver::CMD7(UInt32 rca) +{ + auto request = RealtekSDRequestFactory::CMD7(rca); + + return this->waitForRequest(request); +} + +/// +/// CMD8: Send the interface condition and the voltage supply information +/// +/// @param vhs The voltage supply information +/// @param response The response value on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `__mmc_send_if_cond()` defined in `sd_ops.c`, +/// the caller is responsbile for set the VHS value from the OCR register value. +/// @seealso `IOSDHostDriver::CMD8(vhs:)` if the response can be ignored. +/// +IOReturn IOSDHostDriver::CMD8(UInt8 vhs, SDResponse7& response) +{ + static constexpr UInt8 kCheckPattern = 0xAA; + + // Guard: Send the command + auto request = RealtekSDRequestFactory::CMD8(vhs, kCheckPattern); + + IOReturn retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the CMD8. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Verify the check pattern in the response + auto res = request.command.reinterpretResponseAs(); + + if (res->checkPattern != kCheckPattern) + { + perr("The check pattern in the response is invalid."); + + return kIOReturnInvalid; + } + + response = *res; + + return kIOReturnSuccess; +} + +/// +/// CMD9: Ask the card specified by the given relative address to send the card specific data +/// +/// @param rca The card relative address +/// @param buffer A non-null that stores the **raw** card specific data on return +/// @param length Specify the number of bytes read from the card specific data (must not exceed 16 bytes) +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_send_csd()` defined in `mmc_ops.c`. +/// @note Upon a successful return, the given buffer contains the response data as is. +/// The caller is responsible for dealing with the endianness and parsing the data. +/// @note It is recommended to use `IOSDHostDriver::CMD9(rca:csd:)` to fetch and parse the data in one function call. +/// +IOReturn IOSDHostDriver::CMD9(UInt32 rca, UInt8* buffer, IOByteCount length) +{ + auto request = RealtekSDRequestFactory::CMD9(rca); + + IOReturn retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the CMD9. Error = 0x%x.", retVal); + + return retVal; + } + + length = min(sizeof(CSDVX), length); + + memcpy(buffer, request.command.reinterpretResponseAs()->value, length); + + return kIOReturnSuccess; +} + +/// +/// CMD9: Ask the card specified by the given relative address to send the card specific data and parse the returned data +/// +/// @param rca The card relative address +/// @param csd The card specific data on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_send_csd()` defined in `mmc_ops.c`. +/// @note Upon a successful return, the given buffer contains the response data as is. +/// The caller is responsible for dealing with the endianness and parsing the data. +/// @note It is recommended to use `IOSDHostDriver::CMD9(cid:)` to fetch and parse the data in one function call. +/// +IOReturn IOSDHostDriver::CMD9(UInt32 rca, CSD& csd) +{ + UInt8 buffer[sizeof(CSDVX)] = {}; + + IOReturn retVal = this->CMD9(rca, buffer); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to fetch the card specific data. Error = 0x%x.", retVal); + + return retVal; + } + + // FIXME: Compatibility layer: REMOVE THIS ONCE THE DECODE FUNC IS REWRITTEN + // FIXME: The decode function uses Linux's UNSTUFF_BITS macro to extract bits from the response + // FIXME: This macro assumes that the data is an array of four UInt32 intergers encoded in big endian + UInt32* data = reinterpret_cast(buffer); + + for (auto index = 0; index < sizeof(CSDVX) / sizeof(UInt32); index += 1) + { + data[index] = OSSwapInt32(data[index]); + } + + if (!CSD::decode(data, csd)) + { + perr("Failed to decode the SD configuration register value."); + + return kIOReturnInvalid; + } + + pinfo("Fetched and decoded SD configuration register value successfully."); + + return kIOReturnSuccess; +} + +/// +/// CMD11: Switch to 1.8V bus signaling level +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces a portion of `mmc_set_uhs_voltage()` defined in `mmc_ops.c`. +/// +IOReturn IOSDHostDriver::CMD11() +{ + auto request = RealtekSDRequestFactory::CMD11(); + + IOReturn retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate the CMD11. Error = 0x%x.", retVal); + + return retVal; + } + + if (BitOptions(request.command.reinterpretResponseAs()->getStatus()).contains(R1_ERROR)) + { + perr("The response to the CMD11 has the error bit set."); + + return kIOReturnInvalid; + } + + return kIOReturnSuccess; +} + +/// +/// CMD13: Send the card status +/// +/// @param rca The card relative address +/// @param status The card status on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `__mmc_send_status()` defined in `mmc_ops.c`. +/// +IOReturn IOSDHostDriver::CMD13(UInt32 rca, UInt32& status) +{ + auto request = RealtekSDRequestFactory::CMD13(rca); + + IOReturn retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate the CMD13. Error = 0x%x.", retVal); + + return retVal; + } + + status = request.command.reinterpretResponseAs()->getStatus(); + + return kIOReturnSuccess; +} + +/// +/// CMD55: Tell the card that the next command is an application command +/// +/// @param rca The card relative address +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_app_cmd()` defined in `sd_ops.c`. +/// +IOReturn IOSDHostDriver::CMD55(UInt32 rca) +{ + // Send the command + auto request = RealtekSDRequestFactory::CMD55(rca); + + IOReturn retVal = this->waitForRequest(request); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate the CMD55. Error = 0x%x.", retVal); + + return retVal; + } + + // Check whether the card supports application commands + if (!BitOptions(request.command.reinterpretResponseAs()->getStatus()).contains(R1_APP_CMD)) + { + perr("The card does not support application commands."); + + return kIOReturnUnsupported; + } + + return kIOReturnSuccess; +} + +/// +/// ACMD6: Set the data bus width +/// +/// @param rca The card relative address +/// @param busWidth The data bus width (1 bit or 4 bit) +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_app_set_bus_width()` defined in `sd_ops.c`. +/// +IOReturn IOSDHostDriver::ACMD6(UInt32 rca, IOSDBusConfig::BusWidth busWidth) +{ + // Bus width value + static constexpr UInt32 kSDBusWidth1Bit = 0b00; + static constexpr UInt32 kSDBusWidth4Bit = 0b10; + + // Verify the given bus width + UInt32 busWidthValue; + + switch (busWidth) + { + case IOSDBusConfig::BusWidth::k1Bit: + { + busWidthValue = kSDBusWidth1Bit; + + break; + } + + case IOSDBusConfig::BusWidth::k4Bit: + { + busWidthValue = kSDBusWidth4Bit; + + break; + } + + default: + { + perr("SD does not support the 8-bit bus."); + + return kIOReturnBadArgument; + } + } + + auto request = RealtekSDRequestFactory::ACMD6(busWidthValue); + + return this->waitForAppRequest(request, rca); +} + +/// +/// ACMD13: Send the SD status +/// +/// @param rca The card relative address +/// @param status A non-null buffer that stores the SD status on return +/// @param length Specify the number of bytes read from the response (must not exceed 64 bytes) +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_app_sd_status()` defined in `sd_ops.c`. +/// @note This function allocates an internal 64-byte DMA capable buffer to store the status sent by the card. +/// The status is then copied to the given `status` buffer. +/// +IOReturn IOSDHostDriver::ACMD13(UInt32 rca, UInt8* status, IOByteCount length) +{ + // Allocate a DMA buffer + IODMACommand* dma = this->allocateDMABuffer(64); + + if (dma == nullptr) + { + perr("Failed to allocate a 64-byte DMA buffer."); + + return kIOReturnNoMemory; + } + + // Send the command + // TODO: SET DATA TIMEOUT???? + auto request = RealtekSDRequestFactory::ACMD13(dma); + + IOReturn retVal = this->waitForAppRequest(request, rca); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the ACMD13. Error = 0x%x.", retVal); + + this->releaseDMABuffer(dma); + + return retVal; + } + + // Copy the SD status from the DMA buffer + length = min(length, 64); + + retVal = dma->readBytes(0, status, length) == length ? kIOReturnSuccess : kIOReturnError; + + this->releaseDMABuffer(dma); + + return retVal; +} + +/// +/// ACMD41: Send the operating condition register (OCR) value at the probe stage +/// +/// @param rocr The OCR value returned by the card +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_send_app_op_cond()` defined in `sd_ops.c`. +/// +IOReturn IOSDHostDriver::ACMD41(UInt32& rocr) +{ + auto request = RealtekSDRequestFactory::ACMD41(0); + + IOReturn retVal = this->waitForAppRequest(request, 0); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the ACMD41. Error = 0x%x.", retVal); + + return retVal; + } + + rocr = request.command.reinterpretResponseAs()->getValue(); + + return kIOReturnSuccess; +} + +/// +/// ACMD41: Send the operating condition register (OCR) value +/// +/// @param ocr The OCR value that contains requested settings +/// @param rocr The OCR value returned by the card +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_send_app_op_cond()` defined in `sd_ops.c`. +/// +IOReturn IOSDHostDriver::ACMD41(UInt32 ocr, UInt32& rocr) +{ + auto request = RealtekSDRequestFactory::ACMD41(ocr); + + for (int attempt = 0; attempt < 100; attempt += 1) + { + // Send the command + IOReturn retVal = this->waitForAppRequest(request, 0); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the ACMD41. Error = 0x%x.", retVal); + + return retVal; + } + + // Retrieve the returned OCR value + rocr = request.command.reinterpretResponseAs()->getValue(); + + // Check the busy bit + if (BitOptions(rocr).containsBit(31)) + { + return kIOReturnSuccess; + } + + IOSleep(20); + } + + return kIOReturnTimeout; +} + +/// +/// ACMD51: Ask the card to send the SD configuration register (SCR) value +/// +/// @param rca The card relative address +/// @param buffer A non-null buffer that stores the **raw** SD configuration register value on return +/// @param length Specify the number of bytes read from the response (must not exceed 8 bytes) +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_app_send_scr()` defined in `sd_ops.c`. +/// @note This function allocates an internal 8-byte DMA capable buffer to store the register value sent by the card. +/// The value is then copied to the given `scr` buffer. +/// @note Upon a successful return, the given buffer contains the response data as is. +/// The caller is responsible for dealing with the endianness and parsing the data. +/// @note It is recommended to use `IOSDHostDriver::ACMD51(cid:)` to fetch and parse the data in one function call. +/// +IOReturn IOSDHostDriver::ACMD51(UInt32 rca, UInt8* buffer, IOByteCount length) +{ + // Allocate a DMA buffer + IODMACommand* dma = this->allocateDMABuffer(8); + + if (dma == nullptr) + { + perr("Failed to allocate a 8-byte DMA buffer."); + + return kIOReturnNoMemory; + } + + // Send the command + // TODO: SET DATA TIMEOUT???? + auto request = RealtekSDRequestFactory::ACMD51(dma); + + IOReturn retVal = this->waitForAppRequest(request, rca); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to issue the ACMD51. Error = 0x%x.", retVal); + + this->releaseDMABuffer(dma); + + return retVal; + } + + // Copy the SD status from the DMA buffer + length = min(length, 8); + + retVal = dma->readBytes(0, buffer, length) == length ? kIOReturnSuccess : kIOReturnError; + + this->releaseDMABuffer(dma); + + return retVal; +} + +/// +/// ACMD51: Ask the card to send the SD configuration register (SCR) value and parse the value +/// +/// @param rca The card relative address +/// @param scr The **parsed** SD configuration register value on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `mmc_app_send_scr()` defined in `sd_ops.c`. +/// @note This function allocates an internal 8-byte DMA capable buffer to store the register value sent by the card. +/// The value is then copied to the given `scr` buffer. +/// +IOReturn IOSDHostDriver::ACMD51(UInt32 rca, SCR& scr) +{ + UInt8 buffer[sizeof(SCR)] = {}; + + IOReturn retVal = this->ACMD51(rca, buffer); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to fetch the SD configuration register value. Error = 0x%x.", retVal); + + return retVal; + } + + if (!SCR::decode(buffer, scr)) + { + perr("Failed to decode the SD configuration register value."); + + return kIOReturnInvalid; + } + + pinfo("Fetched and decoded SD configuration register value successfully."); + + return kIOReturnSuccess; +} + +// +// MARK: - Card Management +// + +/// +/// Use the given frequency to communicate with the card and try to attach it +/// +/// @param frequency The initial frequency in Hz +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `mmc_rescan_try_freq()` defined in `core.c` and `mmc_attach_sd()` in `sd.c`. +/// +bool IOSDHostDriver::attachCard(UInt32 frequency) +{ + // Start to attach the card + pinfo("Trying to initialize the card at %u Hz.", frequency); + + psoftassert(this->card == nullptr, "this->card should be null at this moment."); + + this->card = nullptr; + + this->host->setHostInitialClock(frequency); + + UInt32 ocr = this->host->getHostSupportedVoltageRanges(); + + pinfo("Voltage ranges supported by the host: 0x%08x.", ocr); + + // Power up the SD bus + pinfo("Powering up the host bus..."); + + if (this->powerUp(ocr) != kIOReturnSuccess) + { + perr("Failed to power up the host bus."); + + return false; + } + + pinfo("The host bus is now powered up."); + + // Tell the card to go to the idle state + pinfo("Asking the card to go to the idle state..."); + + if (this->CMD0() != kIOReturnSuccess) + { + perr("Failed to tell the card to go to the idle state."); + + return false; + } + + pinfo("The card is now in the idle state."); + + // Check whether a SD card is inserted + if (this->CMD8((ocr & 0xFF8000) != 0) != kIOReturnSuccess) + { + perr("The card does not respond to the CMD8."); + } + + UInt32 rocr; + + if (this->ACMD41(rocr) != kIOReturnSuccess) + { + perr("The card does not respond to the ACMD41."); + + return false; + } + + // Filter out unsupported voltage levels + rocr &= ~0x7FFF; + + rocr = this->selectMutualVoltageLevels(rocr); + + if (rocr == 0) + { + perr("Failed to find a voltage level supported by both the host and the card."); + + return false; + } + + pinfo("Voltage levels supported by both sides = 0x%08x (OCR).", rocr); + + // Start the card initialization sequence + pinfo("Creating the card with the OCR = 0x%08x.", rocr); + + auto card = IOSDCard::createWithOCR(this, rocr); + + if (card == nullptr) + { + perr("Failed to complete the card initialization sequence."); + + return false; + } + + if (!card->attach(this)) + { + perr("Failed to attach the SD card device."); + + card->release(); + + return false; + } + + if (!card->start(this)) + { + perr("Failed to start the SD card device."); + + card->detach(this); + + card->release(); + + return false; + } + + this->card = card; + + // Fetch and publish the card characteristics + // so that the System Profiler can recognize the card and show related information + OSDictionary* characteristics = this->card->getCardCharacteristics(); + + if (characteristics != nullptr) + { + this->getHostDevice()->setProperty("Card Characteristics", characteristics); + } + + OSSafeReleaseNULL(characteristics); + + pinfo("The card has been created and initialized."); + + return true; +} + +/// +/// Publish the block storage device +/// +/// @return `true` on success, `false` otherwise. +/// +bool IOSDHostDriver::publishBlockStorageDevice() +{ + // Initialize and publish the block storage device + pinfo("Publishing the block storage device..."); + + IOSDBlockStorageDevice* device = OSTypeAlloc(IOSDBlockStorageDevice); + + if (device == nullptr) + { + perr("Failed to allocate the block storage device."); + + return false; + } + + if (!device->init(nullptr)) + { + perr("Failed to initialize the block storage device."); + + device->release(); + + return false; + } + + if (!device->attach(this)) + { + perr("Failed to attach the block storage device."); + + device->release(); + + return false; + } + + if (!device->start(this)) + { + perr("Failed to start the block storage device."); + + device->detach(this); + + device->release(); + + return false; + } + + this->blockStorageDevice = device; + + pinfo("The block storage device has been published."); + + return true; +} + +/// +/// Attach the SD card +/// +/// @note This function is invoked on the processor workloop thread when a SD card is inserted. +/// +void IOSDHostDriver::attachCard() +{ + /// Initial card frequencies in Hz + static constexpr UInt32 frequencies[] = { KHz2Hz(400), KHz2Hz(300), KHz2Hz(200), KHz2Hz(100) }; + + pinfo("Attaching the SD card..."); + + ClosedRange range = this->host->getHostClockRange(); + + // Try each default frequency + for (auto frequency : frequencies) + { + pinfo("---------------------------------------------------------------------------"); + + // Guard: Ensure that the default frequency is supported + if (!range.contains(frequency)) + { + perr("Default frequency %d Hz is not supported by the host device.", frequency); + + continue; + } + + // Guard: Attempt to initialize the card + if (!this->attachCard(frequency)) + { + perr("Failed to initialize the card at %u Hz.", frequency); + + pinfo("DEBUG: ABORT EARLY AT HERE."); + + continue; + } + + // The card has been initialized + pinfo("The card has been initialized at %u Hz.", frequency); + + psoftassert(this->publishBlockStorageDevice(), "Failed to publish the block storage device"); + + pinfo("The SD card has been attached."); + + return; + } + + // The default frequencies are not suitable for the host + // Attempt to initialize the card with the maximum frequency supported by the host + perr("Failed to initialize the card at the default frequencies."); + + // Try to initialize the card with the minimum and/or the maximum frequency supported by the host + const UInt32 hostFrequencies[] = { range.lowerBound, range.upperBound }; + + for (auto frequency : hostFrequencies) + { + if (this->attachCard(frequency)) + { + pinfo("Initialized the card at %u Hz.", frequency); + + psoftassert(this->publishBlockStorageDevice(), "Failed to publish the block storage device"); + + return; + } + } + + perr("Failed to initialize the card at the host min and max frequencies [%u, %u] Hz.", range.lowerBound, range.upperBound); +} + +/// +/// Detach the SD card +/// +/// @note This function is invoked on the processor workloop thread when a SD card is removed. +/// +void IOSDHostDriver::detachCard() +{ + // Stop the block storage device + if (this->blockStorageDevice != nullptr) + { + this->blockStorageDevice->stop(this); + + this->blockStorageDevice->detach(this); + + this->blockStorageDevice->release(); + + this->blockStorageDevice = nullptr; + } + + // Stop the card device + if (this->card != nullptr) + { + this->card->stop(this); + + this->card->detach(this); + + this->card->release(); + + this->card = nullptr; + } + + // Recycle all pending requests + while (!this->pendingRequests->isEmpty()) + { + pinfo("Recycling a pending request..."); + + this->releaseBlockRequestToPool(this->pendingRequests->dequeueRequest()); + } +} + +// +// MARK: - Card Events Callbacks +// + +/// +/// [UPCALL] Notify the host driver when a SD card is inserted +/// +/// @note This callback function runs in a gated context provided by the underlying card reader controller. +/// The host device should implement this function without any blocking operations. +/// +void IOSDHostDriver::onSDCardInsertedGated() +{ + // Notify the processor work loop to attach the card + this->attachCardEventSource->enable(); + + // Enable the queue event source to accept incoming block requests + this->queueEventSource->enable(); +} + +/// +/// [UPCALL] Notify the host driver when a SD card is removed +/// +/// @note This callback function runs in a gated context provided by the underlying card reader controller. +/// The host device should implement this function without any blocking operations. +/// +void IOSDHostDriver::onSDCardRemovedGated() +{ + // Disable the queue event source so that the processor work loop will stop processing requests + this->queueEventSource->disable(); + + // Notify the processor work loop to detach the card + this->detachCardEventSource->enable(); +} + +// +// MARK: - Query Card Information +// + +/// +/// Check whether the card has write protection enabled +/// +/// @param result Set `true` if the card is write protected, `false` otherwise. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn IOSDHostDriver::isCardWriteProtected(bool& result) +{ + return this->host->isCardWriteProtected(result); +} + +/// +/// Check whether the card exists +/// +/// @param result Set `true` if the card exists, `false` otherwise. +/// @return `kIOReturnSuccess` always. +/// +IOReturn IOSDHostDriver::isCardPresent(bool& result) +{ + return this->host->isCardPresent(result); +} + +/// +/// Get the card capacity in number of blocks +/// +/// @param nblocks The number of blocks on return +/// @return `kIOReturnSuccess` on success, `kIOReturnNoMedia` if the card is not present. +/// +IOReturn IOSDHostDriver::getCardNumBlocks(UInt64& nblocks) +{ + if (this->card == nullptr) + { + perr("The card is not present."); + + return kIOReturnNoMedia; + } + + nblocks = this->card->getCSD().capacity; + + return kIOReturnSuccess; +} + +/// +/// Get the index of the maximum block of the card +/// +/// @param index The index of the last accessible block on the card on return +/// @return `kIOReturnSuccess` on success, `kIOReturnNoMedia` if the card is not present. +/// +IOReturn IOSDHostDriver::getCardMaxBlockIndex(UInt64& index) +{ + if (this->card == nullptr) + { + perr("The card is not present."); + + return kIOReturnNoMedia; + } + + index = this->card->getCSD().capacity - 1; + + return kIOReturnSuccess; +} + +/// +/// Get the size of a block +/// +/// @param length The block length in bytes on return +/// @return `kIOReturnSuccess` on success, `kIOReturnNoMedia` if the card is not present. +/// +IOReturn IOSDHostDriver::getCardBlockLength(UInt64& length) +{ + if (this->card == nullptr) + { + perr("The card is not present."); + + return kIOReturnNoMedia; + } + + length = 1 << this->card->getCSD().readBlockLength; + + return kIOReturnSuccess; +} + +/// +/// Get the card vendor name +/// +/// @return A non-null static vendor string. +/// @note This function returns "" if the card is not present. +/// +const char* IOSDHostDriver::getCardVendor() +{ + if (this->card == nullptr) + { + perr("The card is not present."); + + return "Realtek"; + } + + return this->card->getCID().getVendorString(); +} + +/// +/// Get the card product name +/// +/// @param name A non-null buffer that can hold at least 8 bytes. +/// @param length The buffer length +/// @return `kIOReturnSuccess` on success, +/// `kIOReturnNoMedia` if the card is not present, +/// `kIOReturnBadArgument` if the buffer is NULL or too small. +/// +IOReturn IOSDHostDriver::getCardName(char* name, IOByteCount length) +{ + if (this->card == nullptr) + { + perr("The card is not present."); + + return kIOReturnNoMedia; + } + + if (!this->card->getCardName(name, length)) + { + perr("The given buffer/length is invalid."); + + return kIOReturnBadArgument; + } + + return kIOReturnSuccess; +} + +/// +/// Get the card revision value +/// +/// @param revision A non-null buffer that can hold at least 8 bytes. +/// @param length The buffer length +/// @return `kIOReturnSuccess` on success, +/// `kIOReturnNoMedia` if the card is not present, +/// `kIOReturnBadArgument` if the buffer is NULL or too small. +/// +IOReturn IOSDHostDriver::getCardRevision(char* revision, IOByteCount length) +{ + if (this->card == nullptr) + { + perr("The card is not present."); + + return kIOReturnNoMedia; + } + + if (!this->card->getCardRevision(revision, length)) + { + perr("The given buffer/length is invalid."); + + return kIOReturnBadArgument; + } + + return kIOReturnSuccess; +} + +/// +/// Get the card manufacture date +/// +/// @param date A non-null buffer that can hold at least 8 bytes. +/// @param length The buffer length +/// @return `kIOReturnSuccess` on success, +/// `kIOReturnNoMedia` if the card is not present, +/// `kIOReturnBadArgument` if the buffer is NULL or too small. +/// +IOReturn IOSDHostDriver::getCardProductionDate(char* date, IOByteCount length) +{ + if (this->card == nullptr) + { + perr("The card is not present."); + + return kIOReturnNoMedia; + } + + if (!this->card->getCardProductionDate(date, length)) + { + perr("The given buffer/length is invalid."); + + return kIOReturnBadArgument; + } + + return kIOReturnSuccess; +} + +/// +/// Get the card serial number +/// +/// @param serial The card serial number on return +/// @return `kIOReturnSuccess` on success, `kIOReturnNoMedia` if the card is not present. +/// +IOReturn IOSDHostDriver::getCardSerialNumber(UInt32& serial) +{ + if (this->card == nullptr) + { + perr("The card is not present."); + + return kIOReturnNoMedia; + } + + serial = this->card->getCID().serial; + + return kIOReturnSuccess; +} + +// +// MARK: - Startup Routines +// + +/// +/// Setup the array of preallocated DMA commands +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupPreallocatedDMACommands() +{ + pinfo("Preallocating an array of DMA commands..."); + + bzero(this->pDMACommands, sizeof(this->pDMACommands)); + + for (auto index = 0; index < IOSDHostDriver::kDefaultPoolSize; index += 1) + { + // TODO: 65536 is the maximum segment size, 524288 (512 * 1024 512KB MAX RAW I/O TRANSFER) is the maximum request size (FETCH FROM HOST CAPS) + this->pDMACommands[index] = IODMACommand::withSpecification(kIODMACommandOutputHost32, 32, 65536, IODMACommand::kMapped, 0, 1); + + if (this->pDMACommands[index] == nullptr) + { + perr("[%02d] Failed to preallocate the DMA command.", index); + + this->tearDownPreallocatedDMACommands(); + + return false; + } + } + + pinfo("Preallocated %u DMA commands successfully.", IOSDHostDriver::kDefaultPoolSize); + + return true; +} + +/// +/// Setup the array of preallocated SD block requests +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupPreallocatedBlockRequests() +{ + pinfo("Preallocating an array of block requests..."); + + bzero(this->pSimpleBlockRequests, sizeof(this->pSimpleBlockRequests)); + + bzero(this->pComplexBlockRequests, sizeof(this->pComplexBlockRequests)); + + for (auto index = 0; index < IOSDHostDriver::kDefaultPoolSize; index += 1) + { + this->pSimpleBlockRequests[index] = OSTypeAlloc(IOSDSimpleBlockRequest); + + this->pComplexBlockRequests[index] = OSTypeAlloc(IOSDComplexBlockRequest); + + if (this->pSimpleBlockRequests[index] == nullptr || + this->pComplexBlockRequests[index] == nullptr) + { + perr("[%02d] Failed to preallocate the block request.", index); + + this->tearDownPreallocatedBlockRequests(); + + return false; + } + } + + pinfo("Preallocated %u SD block requests successfully.", IOSDHostDriver::kDefaultPoolSize); + + return true; +} + +/// +/// Setup the shared work loop to protect the pool and the queue +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupSharedWorkLoop() +{ + pinfo("Creating the shared work loop..."); + + this->sharedWorkLoop = IOWorkLoop::workLoop(); + + if (this->sharedWorkLoop == nullptr) + { + perr("Failed to create the shared work loop."); + + return false; + } + + pinfo("The shared work loop has been created."); + + return true; +} + +/// +/// Setup the DMA command pool +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupDMACommandPool() +{ + pinfo("Creating the DMA command pool..."); + + this->dmaCommandPool = IOCommandPool::withWorkLoop(this->sharedWorkLoop); + + if (this->dmaCommandPool == nullptr) + { + perr("Failed to create the DMA command pool."); + + return false; + } + + pinfo("Populating the pool with preallocated DMA commands..."); + + for (auto index = 0; index < IOSDHostDriver::kDefaultPoolSize; index += 1) + { + this->releaseDMACommandToPool(this->pDMACommands[index]); + } + + pinfo("The DMA command pool has been created."); + + return true; +} + +/// +/// Setup the SD block request pool +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupBlockRequestPool() +{ + pinfo("Creating the block request pool..."); + + this->simpleBlockRequestPool = IOCommandPool::withWorkLoop(this->sharedWorkLoop); + + if (this->simpleBlockRequestPool == nullptr) + { + perr("Failed to create the simple block request pool."); + + return false; + } + + this->complexBlockRequestPool = IOCommandPool::withWorkLoop(this->sharedWorkLoop); + + if (this->complexBlockRequestPool == nullptr) + { + perr("Failed to create the complex block request pool."); + + this->simpleBlockRequestPool->release(); + + this->simpleBlockRequestPool = nullptr; + + return false; + } + + pinfo("Populating the pool with preallocated block requests..."); + + for (auto index = 0; index < IOSDHostDriver::kDefaultPoolSize; index += 1) + { + this->releaseSimpleBlockRequestToPool(this->pSimpleBlockRequests[index]); + + this->releaseComplexBlockRequestToPool(this->pComplexBlockRequests[index]); + } + + pinfo("The block request pool has been created."); + + return true; +} + +/// +/// Setup the SD block request queue +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupBlockRequestQueue() +{ + pinfo("Creating the block request queue..."); + + this->pendingRequests = IOSDBlockRequestQueue::create(this->sharedWorkLoop); + + if (this->pendingRequests == nullptr) + { + perr("Failed to create the request queue."); + + return false; + } + + pinfo("The block request queue has been created."); + + return true; +} + +/// +/// Setup the shared work loop to process card events and block requests +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupProcessorWorkLoop() +{ + pinfo("Creating the dedicated processor work loop..."); + + this->processorWorkLoop = IOWorkLoop::workLoop(); + + if (this->processorWorkLoop == nullptr) + { + perr("Failed to create the dedicated processor work loop."); + + return false; + } + + pinfo("The dedicated processor work loop has been created."); + + return true; +} + +/// +/// Setup the event source that signals the processor work loop to process the block request +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupBlockRequestEventSource() +{ + pinfo("Creating the block request event source..."); + + auto finalizer = OSMemberFunctionCast(IOSDBlockRequestEventSource::Action, this, &IOSDHostDriver::finalizeBlockRequest); + + this->queueEventSource = IOSDBlockRequestEventSource::createWithQueue(this, finalizer, this->pendingRequests); + + if (this->queueEventSource == nullptr) + { + perr("Failed to create the block request event source."); + + return false; + } + + this->queueEventSource->disable(); + + this->processorWorkLoop->addEventSource(this->queueEventSource); + + pinfo("The block request event source has been created and registerd with the processor work loop."); + + return true; +} + +/// +/// Setup the event sources that signal the processor work loop to handle card insertion and removal events +/// +/// @return `true` on success, `false` otherwise. +/// @note Upon an unsuccessful return, all resources allocated by this function are released. +/// +bool IOSDHostDriver::setupCardEventSources() +{ + // Card Insertion Event + pinfo("Creating the card insertion event source..."); + + this->attachCardEventSource = IOSDCardEventSource::createWithAction(this, OSMemberFunctionCast(IOSDCardEventSource::Action, this, &IOSDHostDriver::attachCard)); + + if (this->attachCardEventSource == nullptr) + { + perr("Failed to create the card insertion event source."); + + return false; + } + + // The card insertion event source will be enabled by the notification handler + // @see `IOSDHostDriver::onSDCardInsertedGated()` for details + this->attachCardEventSource->disable(); + + pinfo("The card insertion event source has been created."); + + // Card Removal Event + pinfo("Creating the card removal event source..."); + + this->detachCardEventSource = IOSDCardEventSource::createWithAction(this, OSMemberFunctionCast(IOSDCardEventSource::Action, this, &IOSDHostDriver::detachCard)); + + if (this->detachCardEventSource == nullptr) + { + perr("Failed to create the card insertion event source."); + + this->attachCardEventSource->release(); + + this->attachCardEventSource = nullptr; + + return false; + } + + // The card removal event source will be enabled by the notification handler + // @see `IOSDHostDriver::onSDCardRemovedGated()` for details + this->detachCardEventSource->disable(); + + pinfo("The card insertion event source has been created."); + + // Register with the processor work loop + this->processorWorkLoop->addEventSource(this->attachCardEventSource); + + this->processorWorkLoop->addEventSource(this->detachCardEventSource); + + pinfo("Card event sources have been registered with the processor work loop."); + + return true; +} + +// +// MARK: - Teardown Routines +// + +/// +/// Tear down the array of preallocated DMA commands +/// +void IOSDHostDriver::tearDownPreallocatedDMACommands() +{ + for (auto index = 0; index < IOSDHostDriver::kDefaultPoolSize; index += 1) + { + OSSafeReleaseNULL(this->pDMACommands[index]); + } +} + +/// +/// Tear down the array of preallocated SD block requests +/// +void IOSDHostDriver::tearDownPreallocatedBlockRequests() +{ + for (auto index = 0; index < IOSDHostDriver::kDefaultPoolSize; index += 1) + { + OSSafeReleaseNULL(this->pSimpleBlockRequests[index]); + + OSSafeReleaseNULL(this->pComplexBlockRequests[index]); + } +} + +/// +/// Tear down the shared workloop +/// +void IOSDHostDriver::tearDownSharedWorkLoop() +{ + OSSafeReleaseNULL(this->sharedWorkLoop); +} + +/// +/// Tear down the DMA command pool +/// +void IOSDHostDriver::tearDownDMACommandPool() +{ + // Commands are released by `IOSDHostDriver::tearDownPreallocatedDMACommands()` + OSSafeReleaseNULL(this->dmaCommandPool); +} + +/// +/// Tear down the SD block request pool +/// +void IOSDHostDriver::tearDownBlockRequestPool() +{ + // Block requests are released by `IOSDHostDriver::tearDownPreallocatedBlockRequests()` + OSSafeReleaseNULL(this->simpleBlockRequestPool); + + OSSafeReleaseNULL(this->complexBlockRequestPool); +} + +/// +/// Tear down the SD block request pool +/// +void IOSDHostDriver::tearDownBlockRequestQueue() +{ + OSSafeReleaseNULL(this->pendingRequests); +} + +/// +/// Tear down the processor workloop +/// +void IOSDHostDriver::tearDownProcessorWorkLoop() +{ + OSSafeReleaseNULL(this->processorWorkLoop); +} + +/// +/// Tear down the SD block request event source +/// +void IOSDHostDriver::tearDownBlockRequestEventSource() +{ + if (this->queueEventSource != nullptr) + { + this->queueEventSource->disable(); + + this->processorWorkLoop->removeEventSource(this->queueEventSource); + + this->queueEventSource->release(); + + this->queueEventSource = nullptr; + } +} + +/// +/// Tear down the card event sources +/// +void IOSDHostDriver::tearDownCardEventSources() +{ + if (this->detachCardEventSource != nullptr) + { + this->detachCardEventSource->disable(); + + this->processorWorkLoop->removeEventSource(this->detachCardEventSource); + + this->detachCardEventSource->release(); + + this->detachCardEventSource = nullptr; + } + + if (this->attachCardEventSource != nullptr) + { + this->attachCardEventSource->disable(); + + this->processorWorkLoop->removeEventSource(this->attachCardEventSource); + + this->attachCardEventSource->release(); + + this->attachCardEventSource = nullptr; + } +} + +// +// MARK: - IOService Implementations +// + +/// +/// Start the host driver +/// +/// @param provider An instance of the host device +/// @return `true` on success, `false` otherwise. +/// +bool IOSDHostDriver::start(IOService* provider) +{ + pinfo("===================================================================="); + pinfo("Starting the SD host driver with the device at 0x%08x%08x...", KPTR(provider)); + pinfo("===================================================================="); + + // Start the super class + if (!super::start(provider)) + { + perr("Failed to start the super class."); + + return false; + } + + // Get the host device + this->host = OSDynamicCast(IOSDHostDevice, provider); + + if (this->host == nullptr) + { + perr("The provider is not a valid host device."); + + return false; + } + + this->host->retain(); + + // Preallocate DMA commands + if (!this->setupPreallocatedDMACommands()) + { + goto error; + } + + // Preallocate SD block requests + if (!this->setupPreallocatedBlockRequests()) + { + goto error; + } + + // Setup the shared work loop + if (!this->setupSharedWorkLoop()) + { + goto error; + } + + // Create the DMA command pool + if (!this->setupDMACommandPool()) + { + goto error; + } + + // Create the block request pool + if (!this->setupBlockRequestPool()) + { + goto error; + } + + // Create the request queue + if (!this->setupBlockRequestQueue()) + { + goto error; + } + + // Create the processor work loop + if (!this->setupProcessorWorkLoop()) + { + goto error; + } + + // Create the block request event source + if (!this->setupBlockRequestEventSource()) + { + goto error; + } + + // Create the card insertion and removal event sources + if (!this->setupCardEventSources()) + { + goto error; + } + + pinfo("========================================"); + pinfo("The SD host driver started successfully."); + pinfo("========================================"); + + return true; + +error: + this->tearDownCardEventSources(); + + this->tearDownBlockRequestEventSource(); + + this->tearDownProcessorWorkLoop(); + + this->tearDownBlockRequestQueue(); + + this->tearDownBlockRequestPool(); + + this->tearDownDMACommandPool(); + + this->tearDownSharedWorkLoop(); + + this->tearDownPreallocatedBlockRequests(); + + this->tearDownPreallocatedDMACommands(); + + OSSafeReleaseNULL(this->host); + + pinfo("==================================="); + perr("Failed to start the SD host driver."); + pinfo("==================================="); + + return false; +} + +/// +/// Stop the host driver +/// +/// @param provider An instance of the host device +/// +void IOSDHostDriver::stop(IOService* provider) +{ + this->tearDownCardEventSources(); + + this->tearDownBlockRequestEventSource(); + + this->tearDownProcessorWorkLoop(); + + this->tearDownBlockRequestQueue(); + + this->tearDownBlockRequestPool(); + + this->tearDownDMACommandPool(); + + this->tearDownSharedWorkLoop(); + + this->tearDownPreallocatedBlockRequests(); + + this->tearDownPreallocatedDMACommands(); + + OSSafeReleaseNULL(this->host); + + super::stop(provider); +} diff --git a/RealtekPCIeCardReader/IOSDHostDriver.hpp b/RealtekPCIeCardReader/IOSDHostDriver.hpp new file mode 100644 index 0000000..942d01f --- /dev/null +++ b/RealtekPCIeCardReader/IOSDHostDriver.hpp @@ -0,0 +1,1146 @@ +// +// IOSDHostDriver.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/1/21. +// + +#ifndef IOSDHostDriver_hpp +#define IOSDHostDriver_hpp + +static auto max_segs = 256; +static auto max_seg_size = 65536; // FIREWOLF: SD Command Data Length Limit (UInt16) +static auto max_blk_size = 512; +static auto max_blk_count = 65535; // FIREWOLF: SD Command Data Length Limit (UInt16) Can read up to 65536 blocks in one SD command request +static auto max_req_size = 524288; // FIREWOLF: IODMACommand transfer size limit + +#include +#include "IOSDHostDevice.hpp" +#include "IOSDBusConfig.hpp" +#include "IOSDBlockRequest.hpp" +#include "IOSDSimpleBlockRequest.hpp" +#include "IOSDComplexBlockRequest.hpp" +#include "IOSDBlockRequestQueue.hpp" +#include "IOSDBlockRequestEventSource.hpp" +#include "IOSDCard.hpp" +#include "IOSDCardEventSource.hpp" +#include "SD.hpp" + +/// Forward declaration (Client of the SD host driver) +class IOSDBlockStorageDevice; + +/// Generic SD host device driver +class IOSDHostDriver: public IOService +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareDefaultStructors(IOSDHostDriver); + + using super = IOService; + + // + // MARK: - Private Properties + // + + /// The default pool size + static constexpr IOItemCount kDefaultPoolSize = 32; + + /// Preallocated DMA commands + IODMACommand* pDMACommands[kDefaultPoolSize]; + + /// Preallocated simple SD block requests + IOSDSimpleBlockRequest* pSimpleBlockRequests[kDefaultPoolSize]; + + /// Preallocated complex SD block requests + IOSDComplexBlockRequest* pComplexBlockRequests[kDefaultPoolSize]; + + /// The SD host device (provider) + IOSDHostDevice* host; + + /// The SD block storage device (client) + IOSDBlockStorageDevice* blockStorageDevice; + + /// A shared workloop that serializes the access to the pools and the queues + IOWorkLoop* sharedWorkLoop; + + /// A command pool that contains preallocated DMA commands + IOCommandPool* dmaCommandPool; + + /// A request pool that contains preallocated simple SD block requests + IOCommandPool* simpleBlockRequestPool; + + /// A request pool that contains preallocated complex SD block requests + IOCommandPool* complexBlockRequestPool; + + /// A list of pending requests (a subclass of `IOCommandPool` with convenient methods) + IOSDBlockRequestQueue* pendingRequests; + + /// A dedicated workloop that initializes the card and processes the block request + IOWorkLoop* processorWorkLoop; + + /// An event source to signal the processor workloop to process a pending block request + IOSDBlockRequestEventSource* queueEventSource; + + /// An event source to signal the processor workloop to attach a SD card + IOSDCardEventSource* attachCardEventSource; + + /// An event source to signal the processor workloop to detach a SD card + IOSDCardEventSource* detachCardEventSource; + + /// The SD card (NULL if not inserted) + IOSDCard* card; + +public: + + // + // MARK: - Pool Management + // + + /// + /// Allocate a DMA command from the pool + /// + /// @return A non-null DMA command. + /// + inline IODMACommand* allocateDMACommandFromPool() + { + return OSRequiredCast(IODMACommand, this->dmaCommandPool->getCommand()); + } + + /// + /// Return the given DMA command to the pool + /// + /// @param command The command returned by `IOSDHostDriver::allocateDMACommandFromPool()` + /// + inline void releaseDMACommandToPool(IODMACommand* command) + { + this->dmaCommandPool->returnCommand(command); + } + + /// + /// Allocate a simple block request from the pool + /// + /// @return A non-null simple block request. + /// + inline IOSDSimpleBlockRequest* allocateSimpleBlockRequestFromPool() + { + return OSRequiredCast(IOSDSimpleBlockRequest, this->simpleBlockRequestPool->getCommand()); + } + + /// + /// Return the given simple block request to the pool + /// + /// @param request The request returned by `IOSDHostDriver::allocateBlockRequestFromPool()` + /// + inline void releaseSimpleBlockRequestToPool(IOSDSimpleBlockRequest* request) + { + this->simpleBlockRequestPool->returnCommand(request); + } + + /// + /// Allocate a complex block request from the pool + /// + /// @return A non-null complex block request. + /// + inline IOSDComplexBlockRequest* allocateComplexBlockRequestFromPool() + { + return OSRequiredCast(IOSDComplexBlockRequest, this->complexBlockRequestPool->getCommand()); + } + + /// + /// Return the given simple block request to the pool + /// + /// @param request The request returned by `IOSDHostDriver::allocateBlockRequestFromPool()` + /// + inline void releaseComplexBlockRequestToPool(IOSDComplexBlockRequest* request) + { + this->complexBlockRequestPool->returnCommand(request); + } + + /// + /// Return the given block request to the pool where it belongs + /// + /// @param request The request returned by one of the allocation functions. + /// + inline void releaseBlockRequestToPool(IOSDBlockRequest* request) + { + IOSDComplexBlockRequest* creq = OSDynamicCast(IOSDComplexBlockRequest, request); + + if (creq != nullptr) + { + this->releaseComplexBlockRequestToPool(creq); + + return; + } + + IOSDSimpleBlockRequest* sreq = OSDynamicCast(IOSDSimpleBlockRequest, request); + + if (sreq != nullptr) + { + this->releaseSimpleBlockRequestToPool(sreq); + + return; + } + + pfatal("Detected an invalid type of block request."); + } + + // + // MARK: - I/O Requests + // + + /// + /// Submit the given request to the queue + /// + /// @param processor The processor that services the request + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblocks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn submitBlockRequest(IOSDBlockRequest::Processor processor, IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion); + + /// + /// Submit a request to read a single block + /// + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblocks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The given arguments are guaranteed to be valid. + /// + IOReturn submitReadBlockRequest(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion); + + /// + /// Submit a request to write a single block + /// + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblocks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The given arguments are guaranteed to be valid. + /// + IOReturn submitWriteBlockRequest(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion); + + /// + /// Submit a request to read multiple blocks + /// + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblocks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The given arguments are guaranteed to be valid. + /// + IOReturn submitReadBlocksRequest(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion); + + /// + /// Submit a request to write multiple blocks + /// + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblocks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The given arguments are guaranteed to be valid. + /// + IOReturn submitWriteBlocksRequest(IOMemoryDescriptor* buffer, UInt64 block, UInt64 nblocks, IOStorageAttributes* attributes, IOStorageCompletion* completion); + + /// + /// Process the given request to read a single block + /// + /// @param request A non-null block request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The return value will be passed to the storage completion routine. + /// @note When this function is invoked, the DMA command is guaranteed to be non-null and prepared. + /// + IOReturn processReadBlockRequest(IOSDBlockRequest* request); + + /// + /// Process the given request to read multiple blocks + /// + /// @param request A non-null block request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The return value will be passed to the storage completion routine. + /// @note When this function is invoked, the DMA command is guaranteed to be non-null and prepared. + /// + IOReturn processReadBlocksRequest(IOSDBlockRequest* request); + + /// + /// Process the given request to write a single block + /// + /// @param request A non-null block request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The return value will be passed to the storage completion routine. + /// @note When this function is invoked, the DMA command is guaranteed to be non-null and prepared. + /// + IOReturn processWriteBlockRequest(IOSDBlockRequest* request); + + /// + /// Process the given request to write multiple blocks + /// + /// @param request A non-null block request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The return value will be passed to the storage completion routine. + /// @note When this function is invoked, the DMA command is guaranteed to be non-null and prepared. + /// + IOReturn processWriteBlocksRequest(IOSDBlockRequest* request); + + /// + /// Finalize a request that has been processed + /// + /// @param request A non-null block request + /// @note This function is the completion routine registerd with the block request event source. + /// It deinitializes the given request and puts it back to the block request pool. + /// + void finalizeBlockRequest(IOSDBlockRequest* request); + + // + // MARK: - Query Host Properties + // + + /// + /// Check whether the host supports the High Speed mode + /// + /// @return `true` if the High Speed mode is supported by the host. + /// + bool hostSupportsHighSpeedMode(); + + /// + /// Check whether the host supports the Ultra High Speed mode + /// + /// @return `true` if the Ultra High Speed mode is supported by the host. + /// + bool hostSupportsUltraHighSpeedMode(); + + /// + /// Check whether the host supports the 4-bit bus width + /// + /// @return `true` if the 4-bit bus width is supported by the host. + /// + bool hostSupports4BitBusWidth(); + + /// + /// Get the maximum current setting at its current voltage + /// + /// @return The maximum current in mA. + /// @note Port: This function replaces `sd_get_host_max_current()` defined in `sd.c`. + /// + UInt32 getHostMaxCurrent(); + + /// + /// Select voltage levels mutually supported by the host and the card + /// + /// @param ocr The card OCR value + /// @return The OCR value that contains voltage levels supported by both parties. + /// @note Port: This function replaces `mmc_select_voltage()` defined in `core.c`. + /// + UInt32 selectMutualVoltageLevels(UInt32 ocr); + + /// + /// Get the host device + /// + /// @return The non-null host device. + /// + IOSDHostDevice* getHostDevice(); + + // + // MARK: - Adjust Host Bus Settings + // + + // TODO: RETUNE ENABLE/DISABLE???? + + /// + /// [Shared] Set the bus config + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_set_ios()` defined in `core.c`. + /// + IOReturn setBusConfig(); + + /// + /// Set the initial bus config + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_set_initial_state()` defined in `core.c`. + /// + IOReturn setInitialBusConfig(); + + /// + /// Set the SPI chip select mode + /// + /// @param chipSelect The target mode + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_set_chip_select()` defined in `core.c`. + /// + IOReturn setChipSelect(IOSDBusConfig::ChipSelect chipSelect); + + /// + /// Set the bus speed mode + /// + /// @param timing The target bus speed mode + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_set_timing()` defined in `core.c`. + /// + IOReturn setBusTiming(IOSDBusConfig::BusTiming timing); + + /// + /// Set the bus clock + /// + /// @param clock The target clock frequency in Hz + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_set_clock()` defined in `core.c`. + /// @warning If the given clock frequency is beyond the range of supported clock frequencies, + /// this function will adjust the final clock appropriately. + /// + IOReturn setBusClock(UInt32 clock); + + /// + /// Set the bus width + /// + /// @param width The target bus width + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_set_bus_width()` defined in `core.c`. + /// + IOReturn setBusWidth(IOSDBusConfig::BusWidth width); + + /// + /// Set the signal voltage + /// + /// @param voltage The target signal voltage + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_set_signal_voltage()` defined in `core.c`. + /// + IOReturn setSignalVoltage(IOSDBusConfig::SignalVoltage voltage); + + /// + /// Set the initial signal voltage + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_set_initial_signal_voltage()` defined in `core.c`. + /// + IOReturn setInitialSignalVoltage(); + + /// + /// Set the signal voltage for the Ultra High Speed mode + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_host_set_uhs_voltage()` defined in `core.c`. + /// + IOReturn setUltraHighSpeedSignalVoltage(); + + /// + /// Execute the tuning algorithm + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_execute_tuning()` defined in `core.c`. + /// + IOReturn executeTuning(); + + /// + /// Power up the SD stack + /// + /// @param ocr The OCR value from which to select the VDD value + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_power_up()` defined in `core.c`. + /// + IOReturn powerUp(UInt32 ocr); + + /// + /// Power off the SD stack + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_power_up()` defined in `core.c`. + /// + IOReturn powerOff(); + + /// + /// Reboot the SD stack + /// + /// @param ocr The OCR value from which to select the VDD value + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_power_cycle()` defined in `core.c`. + /// + IOReturn powerCycle(UInt32 ocr); + + // + // MARK: - DMA Utility + // + + /// + /// [Convenient] Allocate a DMA capable buffer + /// + /// @param size The number of bytes + /// @return A non-null IODMACommand instance on success, `nullptr` otherwise. + /// @note The calling thread can be blocked. + /// + IODMACommand* allocateDMABuffer(IOByteCount size); + + /// + /// [Convenient] Release the given DMA capable buffer + /// + /// @param command A non-null IODMACommand instance previously returned by `IOSDHostDriver::allocateDMABuffer()`. + /// + void releaseDMABuffer(IODMACommand* command); + + // + // MARK: - SD Request Center + // + + /// + /// [Helper] Send the given SD command request and wait for the response + /// + /// @param request A SD command request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn waitForRequest(RealtekSDRequest& request); + + /// + /// [Helper] Send the given SD application command request and wait for the response + /// + /// @param request A SD application command request + /// @param rca The card relative address + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_wait_for_app_cmd()` defined in `sd_ops.c`. + /// @note This function issues a CMD55 before sending the given request. + /// + IOReturn waitForAppRequest(RealtekSDRequest& request, UInt32 rca); + + /// + /// CMD0: Reset all cards to the idle state + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_go_idle()` defined in `mmc_ops.c`. + /// + IOReturn CMD0(); + + /// + /// CMD2: Ask any card to send the card identification data + /// + /// @param buffer A non-null that stores the **raw** card identification data on return + /// @param length Specify the number of bytes read from the card identification data (must not exceed 16 bytes) + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_cid()` defined in `mmc_ops.c`. + /// @note Upon a successful return, the given buffer contains the response data as is. + /// The caller is responsible for dealing with the endianness and parsing the data. + /// @note It is recommended to use `IOSDHostDriver::CMD2(cid:)` to fetch and parse the data in one function call. + /// + IOReturn CMD2(UInt8* buffer, IOByteCount length); + + /// + /// CMD2: Ask any card to send the card identification data + /// + /// @param buffer A non-null that stores the **raw** card identification data on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_cid()` defined in `mmc_ops.c`. + /// @note Upon a successful return, the given buffer contains the response data as is. + /// The caller is responsible for dealing with the endianness and parsing the data. + /// @note It is recommended to use `IOSDHostDriver::CMD2(cid:)` to fetch and parse the data in one function call. + /// + template + IOReturn CMD2(UInt8 (&buffer)[N]) + { + return this->CMD2(buffer, N); + } + + /// + /// CMD2: Ask any card to send the card identification data and parse the returned data + /// + /// @param cid The **parsed** card identification data on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_cid()` defined in `mmc_ops.c`. + /// + IOReturn CMD2(CID& cid); + + /// + /// CMD3: Ask the card to publish its relative address + /// + /// @param rca The card relative address on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_relative_addr()` defined in `sd_ops.c`. + /// + IOReturn CMD3(UInt32& rca); + + /// + /// CMD6: Check switchable function or switch the card function + /// + /// @param mode Pass 0 to check switchable function or 1 to switch the card function + /// @param group The function group + /// @param value The function value + /// @param response A non-null buffer that stores the response on return + /// @param length Specify the number of bytes read from the response (must not exceed 64 bytes) + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_sd_switch()` defined in `sd_ops.c`. + /// @note This function allocates an internal 64-byte DMA capable buffer to store the response from the card. + /// The response is then copied to the given `response` buffer. + /// @seealso `IOSDHostDriver::CMD6(mode:group:value:response)` if the caller desires to reuse an existing buffer. + /// + IOReturn CMD6(UInt32 mode, UInt32 group, UInt8 value, UInt8* response, IOByteCount length); + + /// + /// CMD6: Check switchable function or switch the card function + /// + /// @param mode Pass 0 to check switchable function or 1 to switch the card function + /// @param group The function group + /// @param value The function value + /// @param response A non-null buffer that stores the response on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_sd_switch()` defined in `sd_ops.c`. + /// @note This function allocates an internal 64-byte DMA capable buffer to store the response from the card. + /// The response is then copied to the given `response` buffer. + /// @seealso `IOSDHostDriver::CMD6(mode:group:value:response)` if the caller desires to reuse an existing buffer. + /// + template + IOReturn CMD6(UInt32 mode, UInt32 group, UInt8 value, UInt8 (&response)[N]) + { + return this->CMD6(mode, group, value, response, N); + } + + /// + /// CMD6: Check switchable function or switch the card function + /// + /// @param mode Pass 0 to check switchable function or 1 to switch the card function + /// @param group The function group + /// @param value The function value + /// @param response A non-null and prepared memory descriptor that stores the response on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_sd_switch()` defined in `sd_ops.c`. + /// @note This function uses the given response buffer to initiate a DMA read operation. + /// The caller is responsbile for managing the life cycle of the given buffer. + /// + DEPRECATE("Use other CMD6 methods") + IOReturn CMD6(UInt32 mode, UInt32 group, UInt8 value, IOMemoryDescriptor* response); + + /// + /// CMD7: Select a card + /// + /// @param rca The relative address of the card to be selected; + /// Pass 0 to deselect all cards + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `_mmc_select_card()` defined in `sd_ops.c`. + /// + IOReturn CMD7(UInt32 rca); + + /// + /// CMD8: Send the interface condition and the voltage supply information + /// + /// @param vhs The voltage supply information + /// @param response The response value on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `__mmc_send_if_cond()` defined in `sd_ops.c`, + /// the caller is responsbile for set the VHS value from the OCR register value. + /// @seealso `IOSDHostDriver::CMD8(vhs:)` if the response can be ignored. + /// + IOReturn CMD8(UInt8 vhs, SDResponse7& response); + + /// + /// CMD8: Send the interface condition and the voltage supply information + /// + /// @param vhs The voltage supply information + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_if_cond()` defined in `sd_ops.c`, + /// but the caller is responsbile for set the VHS value from the OCR register value. + /// + inline IOReturn CMD8(UInt8 vhs) + { + SDResponse7 response; + + return this->CMD8(vhs, response); + } + + /// + /// CMD9: Ask the card specified by the given relative address to send the card specific data + /// + /// @param rca The card relative address + /// @param buffer A non-null that stores the **raw** card specific data on return + /// @param length Specify the number of bytes read from the card specific data (must not exceed 16 bytes) + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_csd()` defined in `mmc_ops.c`. + /// @note Upon a successful return, the given buffer contains the response data as is. + /// The caller is responsible for dealing with the endianness and parsing the data. + /// @note It is recommended to use `IOSDHostDriver::CMD9(rca:csd:)` to fetch and parse the data in one function call. + /// + IOReturn CMD9(UInt32 rca, UInt8* buffer, IOByteCount length); + + /// + /// CMD9: Ask the card specified by the given relative address to send the card specific data + /// + /// @param rca The card relative address + /// @param buffer A non-null that stores the **raw** card specific data on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_csd()` defined in `mmc_ops.c`. + /// @note Upon a successful return, the given buffer contains the response data as is. + /// The caller is responsible for dealing with the endianness and parsing the data. + /// @note It is recommended to use `IOSDHostDriver::CMD9(rca:csd:)` to fetch and parse the data in one function call. + /// + template + IOReturn CMD9(UInt32 rca, UInt8 (&buffer)[N]) + { + return this->CMD9(rca, buffer, N); + } + + /// + /// CMD9: Ask the card specified by the given relative address to send the card specific data and parse the returned data + /// + /// @param rca The card relative address + /// @param csd The card specific data on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_csd()` defined in `mmc_ops.c`. + /// @note Upon a successful return, the given buffer contains the response data as is. + /// The caller is responsible for dealing with the endianness and parsing the data. + /// @note It is recommended to use `IOSDHostDriver::CMD9(cid:)` to fetch and parse the data in one function call. + /// + IOReturn CMD9(UInt32 rca, CSD& csd); + + /// + /// CMD11: Switch to 1.8V bus signaling level + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces a portion of `mmc_set_uhs_voltage()` defined in `mmc_ops.c`. + /// + IOReturn CMD11(); + + /// + /// CMD13: Send the card status + /// + /// @param rca The card relative address + /// @param status The card status on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `__mmc_send_status()` defined in `mmc_ops.c`. + /// + IOReturn CMD13(UInt32 rca, UInt32& status); + + /// + /// CMD55: Tell the card that the next command is an application command + /// + /// @param rca The card relative address + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_app_cmd()` defined in `sd_ops.c`. + /// + IOReturn CMD55(UInt32 rca); + + /// + /// ACMD6: Set the data bus width + /// + /// @param rca The card relative address + /// @param busWidth The data bus width (1 bit or 4 bit) + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_app_set_bus_width()` defined in `sd_ops.c`. + /// + IOReturn ACMD6(UInt32 rca, IOSDBusConfig::BusWidth busWidth); + + /// + /// ACMD13: Send the SD status + /// + /// @param rca The card relative address + /// @param status A non-null buffer that stores the SD status on return + /// @param length Specify the number of bytes read from the response (must not exceed 64 bytes) + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_app_sd_status()` defined in `sd_ops.c`. + /// @note This function allocates an internal 64-byte DMA capable buffer to store the status sent by the card. + /// The status is then copied to the given `status` buffer. + /// + IOReturn ACMD13(UInt32 rca, UInt8* status, IOByteCount length); + + /// + /// ACMD13: Send the SD status + /// + /// @param rca The card relative address + /// @param status A non-null buffer that stores the SD status on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_app_sd_status()` defined in `sd_ops.c`. + /// @note This function allocates an internal 64-byte DMA capable buffer to store the status sent by the card. + /// The status is then copied to the given `status` buffer. + /// + template + IOReturn ACMD13(UInt32 rca, UInt8 (&status)[N]) + { + return this->ACMD13(rca, status, N); + } + + /// + /// ACMD13: Send the SD status + /// + /// @param rca The card relative address + /// @param status The SD status on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_app_sd_status()` defined in `sd_ops.c`. + /// @note This function allocates an internal 64-byte DMA capable buffer to store the status sent by the card. + /// The status is then copied to the given `status` buffer. + /// + inline IOReturn ACMD13(UInt32 rca, SSR& status) + { + // TODO: PARSE SSR + // TODO: The Linux driver calculates the allocation unit size, erase timeout and erase offset values from the SSR + // TODO: Erase function is not used by the macOS driver at this moment. + return this->ACMD13(rca, reinterpret_cast(&status), sizeof(SSR)); + } + + /// + /// ACMD41: Send the operating condition register (OCR) value at the probe stage + /// + /// @param rocr The OCR value returned by the card + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_app_op_cond()` defined in `sd_ops.c`. + /// + IOReturn ACMD41(UInt32& rocr); + + /// + /// ACMD41: Send the operating condition register (OCR) value + /// + /// @param ocr The OCR value that contains requested settings + /// @param rocr The OCR value returned by the card + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_send_app_op_cond()` defined in `sd_ops.c`. + /// + IOReturn ACMD41(UInt32 ocr, UInt32& rocr); + + /// + /// ACMD51: Ask the card to send the SD configuration register (SCR) value + /// + /// @param rca The card relative address + /// @param buffer A non-null buffer that stores the **raw** SD configuration register value on return + /// @param length Specify the number of bytes read from the response (must not exceed 8 bytes) + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_app_send_scr()` defined in `sd_ops.c`. + /// @note This function allocates an internal 8-byte DMA capable buffer to store the register value sent by the card. + /// The value is then copied to the given `scr` buffer. + /// @note Upon a successful return, the given buffer contains the response data as is. + /// The caller is responsible for dealing with the endianness and parsing the data. + /// @note It is recommended to use `IOSDHostDriver::ACMD51(cid:)` to fetch and parse the data in one function call. + /// + IOReturn ACMD51(UInt32 rca, UInt8* buffer, IOByteCount length); + + /// + /// ACMD51: Ask the card to send the SD configuration register (SCR) value + /// + /// @param rca The card relative address + /// @param buffer The **raw** SD configuration register value on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_app_send_scr()` defined in `sd_ops.c`. + /// @note This function allocates an internal 8-byte DMA capable buffer to store the register value sent by the card. + /// The value is then copied to the given `scr` buffer. + /// @note Upon a successful return, the given buffer contains the response data as is. + /// The caller is responsible for dealing with the endianness and parsing the data. + /// @note It is recommended to use `IOSDHostDriver::ACMD51(cid:)` to fetch and parse the data in one function call. + /// + template + IOReturn ACMD51(UInt32 rca, UInt8 (&buffer)[N]) + { + return this->ACMD51(rca, buffer, N); + } + + /// + /// ACMD51: Ask the card to send the SD configuration register (SCR) value and parse the value + /// + /// @param rca The card relative address + /// @param scr The **parsed** SD configuration register value on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `mmc_app_send_scr()` defined in `sd_ops.c`. + /// @note This function allocates an internal 8-byte DMA capable buffer to store the register value sent by the card. + /// The value is then copied to the given `scr` buffer. + /// + IOReturn ACMD51(UInt32 rca, SCR& scr); + + // + // MARK: - Card Management + // + + /// + /// Use the given frequency to communicate with the card and try to attach it + /// + /// @param frequency The initial frequency in Hz + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces `mmc_rescan_try_freq()` defined in `core.c` and `mmc_attach_sd()` in `sd.c`. + /// + bool attachCard(UInt32 frequency); + + /// + /// Publish the block storage device + /// + /// @return `true` on success, `false` otherwise. + /// + bool publishBlockStorageDevice(); + + /// + /// Attach the SD card + /// + /// @note This function is invoked on the processor workloop thread when a SD card is inserted. + /// + void attachCard(); + + /// + /// Detach the SD card + /// + /// @note This function is invoked on the processor workloop thread when a SD card is removed. + /// + void detachCard(); + + // + // MARK: - Card Events Callbacks + // + + /// + /// [UPCALL] Notify the host driver when a SD card is inserted + /// + /// @note This callback function runs in a gated context provided by the underlying card reader controller. + /// The host device should implement this function without any blocking operations. + /// + void onSDCardInsertedGated(); + + /// + /// [UPCALL] Notify the host driver when a SD card is removed + /// + /// @note This callback function runs in a gated context provided by the underlying card reader controller. + /// The host device should implement this function without any blocking operations. + /// + void onSDCardRemovedGated(); + + // + // MARK: - Query Card Information and Status + // + + /// + /// Check whether the card has write protection enabled + /// + /// @param result Set `true` if the card is write protected, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn isCardWriteProtected(bool& result); + + /// + /// Check whether the card exists + /// + /// @param result Set `true` if the card exists, `false` otherwise. + /// @return `kIOReturnSuccess` always. + /// + IOReturn isCardPresent(bool& result); + + /// + /// Get the card capacity in number of blocks + /// + /// @param nblocks The number of blocks on return + /// @return `kIOReturnSuccess` on success, `kIOReturnNoMedia` if the card is not present. + /// + IOReturn getCardNumBlocks(UInt64& nblocks); + + /// + /// Get the index of the maximum block of the card + /// + /// @param index The index of the last accessible block on the card on return + /// @return `kIOReturnSuccess` on success, `kIOReturnNoMedia` if the card is not present. + /// + IOReturn getCardMaxBlockIndex(UInt64& index); + + /// + /// Get the size of a block + /// + /// @param length The block length in bytes on return + /// @return `kIOReturnSuccess` on success, `kIOReturnNoMedia` if the card is not present. + /// + IOReturn getCardBlockLength(UInt64& length); + + /// + /// Get the card vendor name + /// + /// @return A non-null static vendor string. + /// @note This function returns "Realtek" if the card is not present. + /// + const char* getCardVendor(); + + /// + /// Get the card product name + /// + /// @param name A non-null buffer that can hold at least 8 bytes. + /// @param length The buffer length + /// @return `kIOReturnSuccess` on success, + /// `kIOReturnNoMedia` if the card is not present, + /// `kIOReturnBadArgument` if the buffer is NULL or too small. + /// + IOReturn getCardName(char* name, IOByteCount length); + + /// + /// Get the card revision value + /// + /// @param revision A non-null buffer that can hold at least 8 bytes. + /// @param length The buffer length + /// @return `kIOReturnSuccess` on success, + /// `kIOReturnNoMedia` if the card is not present, + /// `kIOReturnBadArgument` if the buffer is NULL or too small. + /// + IOReturn getCardRevision(char* revision, IOByteCount length); + + /// + /// Get the card manufacture date + /// + /// @param date A non-null buffer that can hold at least 8 bytes. + /// @param length The buffer length + /// @return `kIOReturnSuccess` on success, + /// `kIOReturnNoMedia` if the card is not present, + /// `kIOReturnBadArgument` if the buffer is NULL or too small. + /// + IOReturn getCardProductionDate(char* date, IOByteCount length); + + /// + /// Get the card serial number + /// + /// @param serial The card serial number on return + /// @return `kIOReturnSuccess` on success, `kIOReturnNoMedia` if the card is not present. + /// + IOReturn getCardSerialNumber(UInt32& serial); + + // + // MARK: - Startup Routines + // + + /// + /// Setup the array of preallocated DMA commands + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupPreallocatedDMACommands(); + + /// + /// Setup the array of preallocated SD block requests + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupPreallocatedBlockRequests(); + + /// + /// Setup the shared work loop to protect the pool and the queue + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupSharedWorkLoop(); + + /// + /// Setup the DMA command pool + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupDMACommandPool(); + + /// + /// Setup the SD block request pool + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupBlockRequestPool(); + + /// + /// Setup the SD block request queue + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupBlockRequestQueue(); + + /// + /// Setup the shared work loop to process card events and block requests + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupProcessorWorkLoop(); + + /// + /// Setup the event source that signals the processor work loop to process the block request + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupBlockRequestEventSource(); + + /// + /// Setup the event sources that signal the processor work loop to handle card insertion and removal events + /// + /// @return `true` on success, `false` otherwise. + /// @note Upon an unsuccessful return, all resources allocated by this function are released. + /// + bool setupCardEventSources(); + + // + // MARK: - Teardown Routines + // + + /// + /// Tear down the array of preallocated DMA commands + /// + void tearDownPreallocatedDMACommands(); + + /// + /// Tear down the array of preallocated SD block requests + /// + void tearDownPreallocatedBlockRequests(); + + /// + /// Tear down the shared workloop + /// + void tearDownSharedWorkLoop(); + + /// + /// Tear down the DMA command pool + /// + void tearDownDMACommandPool(); + + /// + /// Tear down the SD block request pool + /// + void tearDownBlockRequestPool(); + + /// + /// Tear down the SD block request pool + /// + void tearDownBlockRequestQueue(); + + /// + /// Tear down the processor workloop + /// + void tearDownProcessorWorkLoop(); + + /// + /// Tear down the SD block request event source + /// + void tearDownBlockRequestEventSource(); + + /// + /// Tear down the card event sources + /// + void tearDownCardEventSources(); + + // + // MARK: - IOService Implementations + // + + /// + /// Start the host driver + /// + /// @param provider An instance of the host device + /// @return `true` on success, `false` otherwise. + /// + bool start(IOService* provider) override; + + /// + /// Stop the host driver + /// + /// @param provider An instance of the host device + /// + void stop(IOService* provider) override; +}; + +#endif /* IOSDHostDriver_hpp */ diff --git a/RealtekPCIeCardReader/IOSDSimpleBlockRequest.cpp b/RealtekPCIeCardReader/IOSDSimpleBlockRequest.cpp new file mode 100644 index 0000000..a571070 --- /dev/null +++ b/RealtekPCIeCardReader/IOSDSimpleBlockRequest.cpp @@ -0,0 +1,226 @@ +// +// IOSDSimpleBlockRequest.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/16/21. +// + +#include "IOSDSimpleBlockRequest.hpp" +#include "IOSDHostDriver.hpp" +#include "Debug.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(IOSDSimpleBlockRequest, IOSDBlockRequest); + +/// +/// Initialize a block request +/// +/// @param driver A non-null host driver that processes the request +/// @param processor A non-null routine to process the request +/// @param buffer The data transfer buffer +/// @param block The starting block number +/// @param nblocks The number of blocks to transfer +/// @param attributes Attributes of the data transfer +/// @param completion The completion routine to call once the data transfer completes +/// @note This function retains the given host driver. +/// The caller must invoke `IOSDBlockRequest::deinit()` before returning the request to the pool. +/// +void IOSDSimpleBlockRequest::init(IOSDHostDriver* driver, + Processor processor, + IOMemoryDescriptor* buffer, + UInt64 block, UInt64 nblocks, + IOStorageAttributes* attributes, + IOStorageCompletion* completion) +{ + this->driver = driver; + + this->driver->retain(); + + this->processor = processor; + + this->buffer = buffer; + + this->command = nullptr; + + this->block = block; + + this->nblocks = nblocks; + + this->attributes = attributes; + + this->completion = *completion; +} + +/// +/// Deinitialize a block request +/// +/// @note The caller must invoke this function to balance the call of `IOSDBlockRequest::init()`. +/// +void IOSDSimpleBlockRequest::deinit() +{ + this->driver->release(); + + this->driver = nullptr; + + this->processor = nullptr; + + this->buffer = nullptr; + + this->command = nullptr; + + this->block = 0; + + this->nblocks = 0; + + this->attributes = nullptr; + + this->completion.target = nullptr; + + this->completion.parameter = nullptr; + + this->completion.action = nullptr; +} + +/// +/// Get the DMA command for data transfer to service the request +/// +/// @note This function is invoked by the processor routine to service the request either fully or partially. +/// +IODMACommand* IOSDSimpleBlockRequest::getDMACommand() +{ + return this->command; +} + +/// +/// Get the index of the start block to service the request +/// +/// @note This function is invoked by the processor routine to service the request either fully or partially. +/// +UInt64 IOSDSimpleBlockRequest::getBlockOffset() +{ + return this->block; +} + +/// +/// Get the number of blocks to service to service the request +/// +/// @note This function is invoked by the processor routine to service the request either fully or partially. +/// +UInt64 IOSDSimpleBlockRequest::getNumBlocks() +{ + return this->nblocks; +} + +/// +/// Service the block request +/// +void IOSDSimpleBlockRequest::service() +{ + // Guard: Prepare the request + IOReturn retVal = this->prepare(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to prepare the request. Error = 0x%x.", retVal); + + goto out; + } + + // Guard: Service the request + passert(this->processor != nullptr, "The processor should not be null."); + + retVal = (*this->processor)(this->driver, this); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to process the request. Error = 0x%x.", retVal); + } + +out: + this->complete(retVal); +} + +/// +/// Prepare the request +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function allocates a DMA command and prepares the buffer accordingly. +/// +IOReturn IOSDSimpleBlockRequest::prepare() +{ + // Guard: Page in and wire down the transfer buffer + pinfo("Page in and wire down the transfer buffer."); + + IOReturn retVal = this->buffer->prepare(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to page in and wire down the transfer buffer. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Retrieve a preallocated DMA command + pinfo("Allocating a DMA command from the pool."); + + IODMACommand* command = this->driver->allocateDMACommandFromPool(); + + passert(command != nullptr, "The DMA command allocated from the pool should not be NULL."); + + // Guard: Associate the transfer buffer with the DMA command + // Note that the DMA command is prepared automatically + retVal = command->setMemoryDescriptor(this->buffer); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to associate the transfer buffer with the DMA command. Error = 0x%x.", retVal); + + this->driver->releaseDMACommandToPool(command); + + return retVal; + } + + // All done + this->command = command; + + pinfo("The transfer buffer is now associated with the DMA command."); + + return kIOReturnSuccess; +} + +/// +/// Complete the request +/// +/// @param retVal The return value passed to the completion routine +/// @note This function releases the DMA command and completes the buffer accordingly, +/// and then invokes the storage completion routine with the given return value. +/// +void IOSDSimpleBlockRequest::complete(IOReturn retVal) +{ + // Deassociate the transfer buffer with the DMA command + pinfo("Completing the request..."); + + if (this->command != nullptr) + { + psoftassert(this->command->clearMemoryDescriptor() == kIOReturnSuccess, + "Failed to deassociate the transfer buffer with the DMA command."); + + psoftassert(this->buffer->complete() == kIOReturnSuccess, + "Failed to complete the transfer buffer."); + + this->driver->releaseDMACommandToPool(this->command); + + this->command = nullptr; + } + + UInt64 actualByteCount = retVal == kIOReturnSuccess ? this->nblocks * 512 : 0; + + pinfo("Invoking the completion routine."); + + IOStorage::complete(&this->completion, retVal, actualByteCount); + + pinfo("The request is completed. Return value = 0x%08x.", retVal); +} diff --git a/RealtekPCIeCardReader/IOSDSimpleBlockRequest.hpp b/RealtekPCIeCardReader/IOSDSimpleBlockRequest.hpp new file mode 100644 index 0000000..4c123ca --- /dev/null +++ b/RealtekPCIeCardReader/IOSDSimpleBlockRequest.hpp @@ -0,0 +1,132 @@ +// +// IOSDSimpleBlockRequest.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/16/21. +// + +#ifndef IOSDSimpleBlockRequest_hpp +#define IOSDSimpleBlockRequest_hpp + +#include "IOSDBlockRequest.hpp" +#include +#include + +/// +/// Represents a simple request to access blocks on a SD card +/// +/// @note The total number of blocks does not exceed the hardware limit, +/// so the driver can service the request in a single DMA transaction. +/// +class IOSDSimpleBlockRequest: public IOSDBlockRequest +{ + /// Constructors & Destructors + OSDeclareDefaultStructors(IOSDSimpleBlockRequest); + + using super = IOSDBlockRequest; + + /// The host driver that processes the request + IOSDHostDriver* driver; + + /// + /// A non-null routine that processes the request + /// + /// @note This allows us to allocate a pool of requests at compile time + /// but deal with different types of requests at runtime. + /// + Processor processor; + + /// The data transfer buffer + IOMemoryDescriptor* buffer; + + /// The DMA command associated with the transfer buffer + IODMACommand* command; + + /// The starting block number + UInt64 block; + + /// The number of blocks to transfer + UInt64 nblocks; + + /// Attributes of the data transfer + IOStorageAttributes* attributes; + + /// The completion routine to call once the data transfer completes + IOStorageCompletion completion; + +public: + /// + /// Initialize a block request + /// + /// @param driver A non-null host driver that processes the request + /// @param processor A non-null routine to process the request + /// @param buffer The data transfer buffer + /// @param block The starting block number + /// @param nblocks The number of blocks to transfer + /// @param attributes Attributes of the data transfer + /// @param completion The completion routine to call once the data transfer completes + /// @note This function retains the given host driver. + /// The caller must invoke `IOSDBlockRequest::deinit()` before returning the request to the pool. + /// + void init(IOSDHostDriver* driver, + Processor processor, + IOMemoryDescriptor* buffer, + UInt64 block, UInt64 nblocks, + IOStorageAttributes* attributes, + IOStorageCompletion* completion) override; + + /// + /// Deinitialize a block request + /// + /// @note The caller must invoke this function to balance the call of `IOSDBlockRequest::init()`. + /// + void deinit() override; + + /// + /// Service the block request + /// + /// @note This function is invoked by the processor work loop to fully service the request. + /// + void service() override; + + /// + /// Get the DMA command for data transfer to service the request + /// + /// @note This function is invoked by the processor routine to service the request either fully or partially. + /// + IODMACommand* getDMACommand() override; + + /// + /// Get the index of the start block to service the request + /// + /// @note This function is invoked by the processor routine to service the request either fully or partially. + /// + UInt64 getBlockOffset() override; + + /// + /// Get the number of blocks to service to service the request + /// + /// @note This function is invoked by the processor routine to service the request either fully or partially. + /// + UInt64 getNumBlocks() override; + +protected: + /// + /// Prepare the request + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function allocates a DMA command and prepares the buffer accordingly. + /// + IOReturn prepare(); + + /// + /// Complete the request + /// + /// @param retVal The return value passed to the completion routine + /// @note This function releases the DMA command and completes the buffer accordingly, + /// and then invokes the storage completion routine with the given return value. + /// + void complete(IOReturn retVal); +}; + +#endif /* IOSDSimpleBlockRequest_hpp */ diff --git a/RealtekPCIeCardReader/Info.plist b/RealtekPCIeCardReader/Info.plist new file mode 100644 index 0000000..a6a4293 --- /dev/null +++ b/RealtekPCIeCardReader/Info.plist @@ -0,0 +1,146 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + IOKitPersonalities + + RealtekRTS524AController + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + IOClass + RealtekRTS524AController + IOPCIMatch + 0x524A10EC + IOProbeScore + 1000 + IOProviderClass + IOPCIDevice + Protocol Characteristics + + Physical Interconnect + Secure Digital + Physical Interconnect Location + Internal + + + RealtekRTS5249Controller + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + IOClass + RealtekRTS5249Controller + IOPCIMatch + 0x524910EC + IOProbeScore + 1000 + IOProviderClass + IOPCIDevice + Protocol Characteristics + + Physical Interconnect + Secure Digital + Physical Interconnect Location + Internal + + + AppleSDXCBlockStorageDevice + + CFBundleIdentifier + com.apple.driver.AppleSDXC + Device Characteristics + + Product Name + Built In SDXC Reader + Vendor Name + Apple + + IOClass + AppleSDXCBlockStorageDevice + IOMediaIcon + + CFBundleIdentifier + com.apple.iokit.IOSCSIArchitectureModelFamily + IOBundleResourceFile + SD.icns + + IOProviderClass + AppleSDXCSlotNull + + IOSDBlockStorageDevice + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + Device Characteristics + + Product Name + Built In SDXC Reader + Vendor Name + Realtek + + IOClass + IOSDBlockStorageDevice + IOMediaIcon + + CFBundleIdentifier + com.apple.iokit.IOSCSIArchitectureModelFamily + IOBundleResourceFile + SD.icns + + IOProbeScore + 1000 + IOProviderClass + IOSDHostDriver + + RealtekRTS525AController + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + IOClass + RealtekRTS525AController + IOPCIMatch + 0x525A10EC + IOProbeScore + 1000 + IOProviderClass + IOPCIDevice + Protocol Characteristics + + Physical Interconnect + Secure Digital + Physical Interconnect Location + Internal + + + + NSHumanReadableCopyright + Copyright © 2021 FireWolf. All rights reserved. + OSBundleLibraries + + com.apple.iokit.IOPCIFamily + 2.9 + com.apple.iokit.IOStorageFamily + 2.1 + com.apple.kpi.iokit + 16.4 + com.apple.kpi.libkern + 16.4 + + + diff --git a/RealtekPCIeCardReader/RealtekPCIeCardReaderController.cpp b/RealtekPCIeCardReader/RealtekPCIeCardReaderController.cpp new file mode 100644 index 0000000..a081e9d --- /dev/null +++ b/RealtekPCIeCardReader/RealtekPCIeCardReaderController.cpp @@ -0,0 +1,3249 @@ +// +// RealtekPCIeCardReaderController.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/18/21. +// + +#include "RealtekPCIeCardReaderController.hpp" +#include "RealtekSDXCSlot.hpp" +#include "BitOptions.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndAbstractStructors(RealtekPCIeCardReaderController, AppleSDXC); + +// +// MARK: - Access Memory Mapped Registers (Generic, Final) +// + +/// +/// Read a byte from a memory mapped register at the given address +/// +/// @param address The register address +/// @return The register value. +/// @note Port: This function replaces the macro `rtsx_pci_readb()` defined in `rtsx_pci.h`. +/// +UInt8 RealtekPCIeCardReaderController::readRegister8(UInt32 address) +{ + return this->readRegister(address); +} + +/// +/// Read a word from a memory mapped register at the given address +/// +/// @param address The register address +/// @return The register value. +/// @note Port: This function replaces the macro `rtsx_pci_readw()` defined in `rtsx_pci.h`. +/// +UInt16 RealtekPCIeCardReaderController::readRegister16(UInt32 address) +{ + return this->readRegister(address); +} + +/// +/// Read a double word from a memory mapped register at the given address +/// +/// @param address The register address +/// @return The register value. +/// @note Port: This function replaces the macro `rtsx_pci_readl()` defined in `rtsx_pci.h`. +/// +UInt32 RealtekPCIeCardReaderController::readRegister32(UInt32 address) +{ + return this->readRegister(address); +} + +/// +/// Write a byte to a memory mapped register at the given address +/// +/// @param address The register address +/// @param value The register value +/// @note Port: This function replaces the macro `rtsx_pci_writeb()` defined in `rtsx_pci.h`. +/// +void RealtekPCIeCardReaderController::writeRegister8(UInt32 address, UInt8 value) +{ + this->writeRegister(address, value); +} + +/// +/// Write a word to a memory mapped register at the given address +/// +/// @param address The register address +/// @param value The register value +/// @note Port: This function replaces the macro `rtsx_pci_writew()` defined in `rtsx_pci.h`. +/// +void RealtekPCIeCardReaderController::writeRegister16(UInt32 address, UInt16 value) +{ + this->writeRegister(address, value); +} + +/// +/// Write a double word to a memory mapped register at the given address +/// +/// @param address The register address +/// @param value The register value +/// @note Port: This function replaces the macro `rtsx_pci_writel()` defined in `rtsx_pci.h`. +/// +void RealtekPCIeCardReaderController::writeRegister32(UInt32 address, UInt32 value) +{ + this->writeRegister(address, value); +} + +// +// MARK: - Access Chip Registers (Generic, Final) +// + +/// +/// Read a byte from the chip register at the given address +/// +/// @param address The register address +/// @param value The register value on return +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out. +/// @note Port: This function replaces `rtsx_pci_read_register()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::readChipRegister(UInt16 address, UInt8& value) +{ + // The driver will program MMIO registers to access chip registers + using namespace RTSX::MMIO; + + pinfo("Reading the chip register at 0x%04x...", address); + + // Start the operation by writing the address with busy bit set to the chip + this->writeRegister32(rHAIMR, HAIMR::RegValueForReadOperation(address)); + + // Wait until the device is free to finish the operation + for (auto attempt = 0; attempt < HAIMR::kMaxAttempts; attempt += 1) + { + UInt32 regVal = this->readRegister32(rHAIMR); + + if (!HAIMR::IsBusy(regVal)) + { + // Extract the register value + value = static_cast(regVal & 0xFF); + + return kIOReturnSuccess; + } + } + + // Timed out + perr("Timed out when reading the chip register at 0x%04x.", address); + + return kIOReturnTimeout; +} + +/// +/// Write a byte to the chip register at the given address +/// +/// @param address The register address +/// @param mask The register value mask +/// @param value The register value +/// @return `kIOReturnSuccess` on success; +/// `kIOReturnTimeout` if timed out; +/// `kIOReturnError` if the new register value is not `value`. +/// @note Port: This function replaces `rtsx_pci_write_register()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::writeChipRegister(UInt16 address, UInt8 mask, UInt8 value) +{ + // The driver will program MMIO registers to access chip registers + using namespace RTSX::MMIO; + + pinfo("Writing 0x%02x with mask 0x%02x to the chip register at 0x%04x...", value, mask, address); + + // Start the operation by writing the address, mask and value with busy and write bits set to the chip + this->writeRegister32(rHAIMR, HAIMR::RegValueForWriteOperation(address, mask, value)); + + // Wait until the device is free to finish the operation + for (auto attempt = 0; attempt < HAIMR::kMaxAttempts; attempt += 1) + { + UInt32 regVal = this->readRegister32(rHAIMR); + + if (HAIMR::IsBusy(regVal)) + { + continue; + } + + // The device is now free + // Guard: Extract and verify the new register value + regVal &= 0xFF; + + if (regVal != value) + { + perr("The new register value is 0x%02x and does not match the given value 0x%02x.", regVal, value); + + return kIOReturnError; + } + + // All done + return kIOReturnSuccess; + } + + // Timed out + perr("Timed out when writing 0x%02x with mask 0x%02x to the chip register at 0x%04x.", value, mask, address); + + return kIOReturnTimeout; +} + +/// +/// Write to multiple chip registers conveniently +/// +/// @param pairs A sequence of pairs of register address and value +/// @return `kIOReturnSuccess` on success; +/// `kIOReturnTimeout` if timed out; +/// `kIOReturnError` if the new register value is not `value`. +/// +IOReturn RealtekPCIeCardReaderController::writeChipRegisters(const ChipRegValuePairs& pairs) +{ + IOReturn retVal = kIOReturnSuccess; + + IOItemCount count = pairs.size(); + + pinfo("Writing %u chip registers...", count); + + for (auto index = 0; index < count; index += 1) + { + ChipRegValuePair pair = pairs.get(index); + + retVal = this->writeChipRegister(pair.address, pair.mask, pair.value); + + if (retVal != kIOReturnSuccess) + { + perr("[%02d] Failed to write 0x%02x to register 0x%04x. Error = 0x%x.", index, pair.value, pair.address, retVal); + + return retVal; + } + } + + pinfo("Wrote %u chip registers. Returns 0x%x.", count, retVal); + + return retVal; +} + +/// +/// Dump the chip registers in the given range +/// +/// @param range The range of register addresses +/// +void RealtekPCIeCardReaderController::dumpChipRegisters(ClosedRange range) +{ + pinfo("Dumping chip registers from 0x%04x to 0x%04x.", range.lowerBound, range.upperBound); + + UInt8 value = 0; + + for (auto address = range.lowerBound; address <= range.upperBound; address += 1) + { + if (this->readChipRegister(address, value) != kIOReturnSuccess) + { + pinfo("[0x%04d] 0x%02x", address, value); + } + else + { + pinfo("[0x%04d] ERROR", address); + } + } +} + +// +// MARK: - Access Physical Layer Registers (Default, Overridable) +// + +/// +/// Read a word from the physical layer register at the given address +/// +/// @param address The register address +/// @param value The register value on return +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_read_phy_register()` defined in `rtsx_psr.c`. +/// @note Subclasses may override this function to provide a device-specific implementation. +/// +IOReturn RealtekPCIeCardReaderController::readPhysRegister(UInt8 address, UInt16& value) +{ + // The driver will program chip registers to access PHY registers + using namespace RTSX::Chip; + + pinfo("Read the PHY register at 0x%02x.", address); + + // Guard: Initiate the operation + IOReturn retVal; + + const ChipRegValuePair pairs[] = + { + // Set the address of the register + { PHY::rADDR, address }, + + // Set the data direction + { PHY::rRWCTL, PHY::RWCTL::kSetReadOperation } + }; + + retVal = this->writeChipRegisters(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate the operation to read the PHY register at 0x%02x.", address); + + return retVal; + } + + // Wait until the device is free to finish the operation + for (auto attempt = 0; attempt < PHY::RWCTL::kMaximumAttempts; attempt += 1) + { + // The device status + UInt8 regVal; + + // Guard: Fetch the device status + retVal = this->readChipRegister(PHY::rRWCTL, regVal); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to fetch the device status. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Verify the device status + if (PHY::RWCTL::IsBusy(regVal)) + { + continue; + } + + // The device is now free + // Fetch the register value + UInt8 l8, h8; + + // Guard: Fetch the low 8 bits + retVal = this->readChipRegister(PHY::rDATA0, l8); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to fetch the low 8 bits of the register value. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Fetch the high 8 bits + retVal = this->readChipRegister(PHY::rDATA1, h8); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to fetch the high 8 bits of the register value. Error = 0x%x.", retVal); + + return retVal; + } + + // All done + value = static_cast(h8) << 8 | l8; + + return kIOReturnSuccess; + } + + // Timed out + perr("Timed out when reading the PHY regiater at 0x%02x.", address); + + return kIOReturnTimeout; +} + +/// +/// Write a word to the physical layer register at the given address +/// +/// @param address The register address +/// @param value The register value +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_write_phy_register()` defined in `rtsx_psr.c`. +/// @note Subclasses may override this function to provide a device-specific implementation. +/// +IOReturn RealtekPCIeCardReaderController::writePhysRegister(UInt8 address, UInt16 value) +{ + // The driver will program chip registers to access PHY registers + using namespace RTSX::Chip; + + pinfo("Write 0x%02x to the PHY register at 0x%02x.", value, address); + + // Guard: Initiate the operation + pinfo("Initiating the PHY write operation..."); + + IOReturn retVal; + + const ChipRegValuePair pairs[] = + { + // Set the low 8 bits of the new register value + { PHY::rDATA0, static_cast(value & 0xFF) }, + + // Set the high 8 bits of the new register value + { PHY::rDATA1, static_cast(value >> 8) }, + + // Set the address of the register + { PHY::rADDR, address }, + + // Set the data direction + { PHY::rRWCTL, PHY::RWCTL::kSetWriteOperation } + }; + + retVal = this->writeChipRegisters(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate the operation to write 0x%04x to the PHY register at 0x%02x.", value, address); + + return retVal; + } + + pinfo("Informed the card reader the register address and the value."); + + // Wait until the device is free to finish the operation + for (auto attempt = 0; attempt < PHY::RWCTL::kMaximumAttempts; attempt += 1) + { + // The device status + UInt8 regVal; + + // Guard: Fetch the device status + retVal = this->readChipRegister(PHY::rRWCTL, regVal); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to fetch the device status. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Verify the device status + if (!PHY::RWCTL::IsBusy(regVal)) + { + return kIOReturnSuccess; + } + } + + // Timed out + perr("Timed out when writing the PHY register at 0x%02x.", address); + + return kIOReturnTimeout; +} + +/// +/// Write to multiple physical layer registers conveniently +/// +/// @param pairs An array of registers and their values +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// +IOReturn RealtekPCIeCardReaderController::writePhysRegisters(const PhysRegValuePair* pairs, size_t count) +{ + pinfo("Writing %lu PHY registers...", count); + + for (auto index = 0; index < count; index += 1) + { + auto retVal = this->writePhysRegister(pairs[index].address, pairs[index].value); + + if (retVal != kIOReturnSuccess) + { + return retVal; + } + } + + return kIOReturnSuccess; +} + +/// +/// Modify a physical layer register conveniently +/// +/// @param address The register address +/// @param modifier A modifier that takes the current register value and return the new value +/// @param context A nullable context that will be passed to the modifier function +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_update_phy()` defined in `rtsx_pci.h`. +/// +IOReturn RealtekPCIeCardReaderController::modifyPhysRegister(UInt8 address, PhysRegValueModifier modifier, const void* context) +{ + UInt16 regVal; + + // Guard: Read the current register value + IOReturn retVal = this->readPhysRegister(address, regVal); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to read the current register value."); + + return retVal; + } + + // Modify the register value + regVal = (*modifier)(regVal, context); + + // Guard: Write the new register value + retVal = this->writePhysRegister(address, regVal); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to write the new register value."); + } + + return retVal; +} + +/// +/// Set a bit at the given index in a physical layer register conveniently +/// +/// @param address The register address +/// @param index The zero-based bit index +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// +IOReturn RealtekPCIeCardReaderController::setPhysRegisterBitAtIndex(UInt8 address, UInt32 index) +{ + // Guard: The given bit index must be valid + if (index >= sizeof(UInt16) * 8) + { + perr("The given bit index %d is invalid.", index); + + return kIOReturnBadArgument; + } + + // Modify the register value + auto modifier = [](UInt16 regVal, const void* context) -> UInt16 + { + return regVal | 1 << *reinterpret_cast(context); + }; + + return this->modifyPhysRegister(address, modifier, &index); +} + +/// +/// Clear a bit at the given index in a physical layer register conveniently +/// +/// @param address The register address +/// @param index The zero-based bit index +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// +IOReturn RealtekPCIeCardReaderController::clearPhysRegisterBitAtIndex(UInt8 address, UInt32 index) +{ + // Guard: The given bit index must be valid + if (index >= sizeof(UInt16) * 8) + { + perr("The given bit index %d is invalid.", index); + + return kIOReturnBadArgument; + } + + // Modify the register value + auto modifier = [](UInt16 regVal, const void* context) -> UInt16 + { + return regVal & ~(1 << *reinterpret_cast(context)); + }; + + return this->modifyPhysRegister(address, modifier, &index); +} + +/// +/// Append the given value to a physical layer register conveniently +/// +/// @param address The register address +/// @param mask The register value mask +/// @param append The value to be appended to the current value +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_update_phy()` defined in `rtsx_pci.h`. +/// +IOReturn RealtekPCIeCardReaderController::appendPhysRegister(UInt8 address, UInt16 mask, UInt16 append) +{ + Pair pair = { mask, append }; + + auto modifier = [](UInt16 regVal, const void* context) -> UInt16 + { + auto pair = reinterpret_cast*>(context); + + return (regVal & pair->first) | pair->second; + }; + + return this->modifyPhysRegister(address, modifier, &pair); +} + +/// +/// Append each given value to the corresponding physical layer register conveniently +/// +/// @param pairs An array of registers and their masks and values +/// @param count The number of elements in the array +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// +IOReturn RealtekPCIeCardReaderController::appendPhysRegisters(const PhysRegMaskValuePair* pairs, size_t count) +{ + IOReturn retVal = kIOReturnSuccess; + + for (auto index = 0; index < count; index += 1) + { + retVal = this->appendPhysRegister(pairs[index].address, pairs[index].mask, pairs[index].value); + + if (retVal != kIOReturnSuccess) + { + break; + } + } + + return retVal; +} + +// +// MARK: - Host Buffer Management +// + +/// +/// Read from the host command buffer into the given buffer +/// +/// @param offset A byte offset into the host buffer +/// @param buffer A non-null buffer +/// @param length The number of bytes to read +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function coordinates all accesses to the host buffer. +/// +IOReturn RealtekPCIeCardReaderController::readHostBuffer(IOByteCount offset, void* buffer, IOByteCount length) +{ + // Guard: The given buffer must be non-null + if (buffer == nullptr) + { + perr("The given buffer is NULL."); + + return kIOReturnBadArgument; + } + + // Guard: The number of bytes to read must not exceed 4096 bytes + if (offset + length > RealtekPCIeCardReaderController::kHostBufferSize) + { + perr("Detected an out-of-bounds read: Request to read %llu bytes at offset %llu.", length, offset); + + return kIOReturnBadArgument; + } + + // The read routine will run in a gated context + auto action = [](OSObject* controller, void* offset, void* buffer, void* length, void*) -> IOReturn + { + // Retrieve the controller instance + auto instance = OSDynamicCast(RealtekPCIeCardReaderController, controller); + + passert(instance != nullptr, "The controller instance is invalid."); + + auto off = *reinterpret_cast(offset); + + auto len = *reinterpret_cast(length); + + passert(instance->hostBufferDMACommand->readBytes(off, buffer, len) == len, + "Should be able to read from the host buffer at this moment."); + + return kIOReturnSuccess; + }; + + return this->commandGate->runAction(action, &offset, buffer, &length); +} + +/// +/// Write to the host buffer form the given buffer +/// +/// @param offset A byte offset into the host buffer +/// @param buffer A non-null buffer +/// @param length The number of bytes to write +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function coordinates all accesses to the host buffer. +/// +IOReturn RealtekPCIeCardReaderController::writeHostBuffer(IOByteCount offset, const void* buffer, IOByteCount length) +{ + // Guard: The given buffer must be non-null + if (buffer == nullptr) + { + perr("The given buffer is NULL."); + + return kIOReturnBadArgument; + } + + // Guard: The number of bytes to read must not exceed 4096 bytes + if (offset + length > RealtekPCIeCardReaderController::kHostBufferSize) + { + perr("Detected an out-of-bounds write: Request to write %llu bytes at offset %llu.", length, offset); + + return kIOReturnBadArgument; + } + + // The write routine will run in a gated context + auto action = [](OSObject* controller, void* offset, void* buffer, void* length, void*) -> IOReturn + { + // Retrieve the controller instance + auto instance = OSDynamicCast(RealtekPCIeCardReaderController, controller); + + passert(instance != nullptr, "The controller instance is invalid."); + + auto off = *reinterpret_cast(offset); + + auto len = *reinterpret_cast(length); + + passert(instance->hostBufferDMACommand->writeBytes(off, buffer, len) == len, + "Should be able to write to the host buffer at this moment."); + + return kIOReturnSuccess; + }; + + return this->commandGate->runAction(action, &offset, const_cast(buffer), &length); +} + +/// +/// Dump the host buffer contents +/// +/// @param offset A byte offset into the host data buffer +/// @param length The number of bytes to dump +/// @param column The number of columns to print +/// +void RealtekPCIeCardReaderController::dumpHostBuffer(IOByteCount offset, IOByteCount length, IOByteCount column) +{ + UInt8* buffer = reinterpret_cast(IOMalloc(length)); + + this->readHostBuffer(offset, buffer, length); + + pbuf(buffer, length, column); + + IOFree(buffer, length); +} + +// +// MARK: - Host Command Management +// + +/// +/// Start a new host command transfer session +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note The caller must invoke this function before any subsequent calls to `enqueue*Command()`. +/// Any commands enqueued before this function call will be overwritten. +/// Once all commands are enqueued, the caller should invoke `endCommandTransfer()` to send commands to the device. +/// @note Port: This function replaces `rtsx_pci_init_cmd()` defined in `rtsx_pci.h`. +/// +IOReturn RealtekPCIeCardReaderController::beginCommandTransfer() +{ + // The reset counter routine will run in a gated context + auto action = [](OSObject* controller, void*, void*, void*, void*) -> IOReturn + { + // Retrieve the controller instance + auto instance = OSDynamicCast(RealtekPCIeCardReaderController, controller); + + passert(instance != nullptr, "The controller instance is invalid."); + + // Reset the counter + instance->hostCommandCount = 0; + + return kIOReturnSuccess; + }; + + pinfo("Begin a command transfer session."); + + return this->commandGate->runAction(action); +} + +/// +/// Enqueue a command +/// +/// @param command The command +/// @return `kIOReturnSuccess` on success, `kIOReturnBusy` if the command buffer is full, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_add_cmd()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::enqueueCommand(const Command& command) +{ + // The enqueue routine will run in a gated context + auto action = [](OSObject* controller, void* command, void*, void*, void*) -> IOReturn + { + // Retrieve the controller instance + auto instance = OSDynamicCast(RealtekPCIeCardReaderController, controller); + + passert(instance != nullptr, "The controller instance is invalid."); + + // Guard: Ensure that the queue is not full + if (instance->hostCommandCount >= RTSX::MMIO::HCBAR::kMaxNumCmds) + { + pwarning("The host command buffer queue is full."); + + return kIOReturnBusy; + } + + // Retrieve and write the command value + UInt64 offset = instance->hostCommandCount * sizeof(UInt32); + + UInt32 value = OSSwapHostToLittleInt32(reinterpret_cast(command)->value); + + passert(instance->hostBufferDMACommand->writeBytes(offset, &value, sizeof(UInt32)) == sizeof(UInt32), + "Should be able to write the command value to the host buffer at this moment."); + + instance->hostCommandCount += 1; + + return kIOReturnSuccess; + }; + + return this->commandGate->runAction(action, const_cast(reinterpret_cast(&command))); +} + +/// +/// Enqueue a command to read the register at the given address conveniently +/// +/// @param address The register address +/// @return `kIOReturnSuccess` on success, `kIOReturnBusy` if the command buffer is full. +/// @note Port: This function replaces `rtsx_pci_add_cmd()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::enqueueReadRegisterCommand(UInt16 address) +{ + pinfo("Enqueue: Address = 0x%04x; Mask = 0x%02x; Value = 0x%02x.", address, 0, 0); + + return this->enqueueCommand(Command(Command::kReadRegister, address, 0, 0)); +} + +/// +/// Enqueue a command to write a value to the register at the given address conveniently +/// +/// @param address The register address +/// @param mask The register value mask +/// @param value The register value +/// @return `kIOReturnSuccess` on success, `kIOReturnBusy` if the command buffer is full. +/// @note Port: This function replaces `rtsx_pci_add_cmd()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::enqueueWriteRegisterCommand(UInt16 address, UInt8 mask, UInt8 value) +{ + pinfo("Enqueue: Address = 0x%04x; Mask = 0x%02x; Value = 0x%02x.", address, mask, value); + + return this->enqueueCommand(Command(Command::kWriteRegister, address, mask, value)); +} + +/// +/// Enqueue a command to check the value of the register at the given address conveniently +/// +/// @param address The register address +/// @param mask The register value mask +/// @param value The register value +/// @return `kIOReturnSuccess` on success, `kIOReturnBusy` if the command buffer is full. +/// @note Port: This function replaces `rtsx_pci_add_cmd()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::enqueueCheckRegisterCommand(UInt16 address, UInt8 mask, UInt8 value) +{ + pinfo("Enqueue: Address = 0x%04x; Mask = 0x%02x; Value = 0x%02x.", address, mask, value); + + return this->enqueueCommand(Command(Command::kCheckRegister, address, mask, value)); +} + +/// +/// Finish the existing host command transfer session +/// +/// @param timeout Specify the amount of time in milliseconds +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_send_cmd()` defined in `rtsx_psr.c`. +/// @note This function sends all commands in the queue to the device. +/// +IOReturn RealtekPCIeCardReaderController::endCommandTransfer(UInt32 timeout) +{ + // The transfer routine will run in a gated context + // Returns `kIOReturnTimeout` if the transfer has timed out; + // `kIOReturnSuccess` if the transfer has succeeded; + // `kIOReturnError` otherwise. + auto action = [](OSObject* controller, void* timeout, void*, void*, void*) -> IOReturn + { + // Retrieve the controller instance + auto instance = OSDynamicCast(RealtekPCIeCardReaderController, controller); + + passert(instance != nullptr, "The controller instance is invalid."); + + // Tell the device the location of the command buffer and to start executing commands + using namespace RTSX::MMIO; + + instance->hostBufferTransferStatus = kIOReturnNotReady; + + instance->writeRegister32(rHCBAR, instance->hostBufferAddress); + + instance->writeRegister32(rHCBCTLR, HCBCTLR::RegValueForStartCommand(instance->hostCommandCount)); + + // Set up the timer + passert(instance->hostBufferTimer != nullptr, "The host buffer timer should not be NULL."); + + passert(instance->hostBufferTimer->setTimeoutMS(*reinterpret_cast(timeout)) == kIOReturnSuccess, "Should be able to set the timeout."); + + // Wait for the transfer result + // Block the current thread and release the gate + // Either the timeout handler or the interrupt handler will modify the status and wakeup the current thread + instance->commandGate->commandSleep(&instance->hostBufferTransferStatus); + + // When the sleep function returns, the transfer is done + return instance->hostBufferTransferStatus; + }; + + IOReturn retVal = this->commandGate->runAction(action, &timeout); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to transfer host commands. Error = 0x%x.", retVal); + + psoftassert(this->stopTransfer() == kIOReturnSuccess, + "Failed to terminate the current transfer session."); + } + + pinfo("Finished a command transfer session. Returns 0x%x.", retVal); + + return retVal; +} + +/// +/// Finish the existing host command transfer session without waiting for the completion interrupt +/// +/// @note Port: This function replaces `rtsx_pci_send_cmd_no_wait()` defined in `rtsx_psr.c`. +/// @note This function sends all commands in the queue to the device. +/// +void RealtekPCIeCardReaderController::endCommandTransferNoWait() +{ + // The transfer routine will run in a gated context + auto action = [](OSObject* controller, void* timeout, void*, void*, void*) -> IOReturn + { + // Retrieve the controller instance + auto instance = OSDynamicCast(RealtekPCIeCardReaderController, controller); + + passert(instance != nullptr, "The controller instance is invalid."); + + // Tell the device the location of the command buffer and to start executing commands + using namespace RTSX::MMIO; + + instance->hostBufferTransferStatus = kIOReturnNotReady; + + instance->writeRegister32(rHCBAR, instance->hostBufferAddress); + + instance->writeRegister32(rHCBCTLR, HCBCTLR::RegValueForStartCommand(instance->hostCommandCount)); + + return kIOReturnSuccess; + }; + + this->commandGate->runAction(action); +} + +/// +/// Enqueue a sequence of commands to read registers conveniently +/// +/// @param pairs A sequence of pairs of register address and value +/// @return `kIOReturnSuccess` on success, `kIOReturnError` otherwise. +/// @note This function provides a elegant way to enqueue multiple commands and handle errors. +/// +IOReturn RealtekPCIeCardReaderController::enqueueReadRegisterCommands(const ChipRegValuePairs& pairs) +{ + IOItemCount count = pairs.size(); + + for (auto index = 0; index < count; index += 1) + { + auto retVal = this->enqueueReadRegisterCommand(pairs.get(index).address); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue the command at index %d. Error = 0x%x.", index, retVal); + + return retVal; + } + } + + return kIOReturnSuccess; +} + +/// +/// Enqueue a sequence of commands to write registers conveniently +/// +/// @param pairs A sequence of pairs of register address and value +/// @return `kIOReturnSuccess` on success, `kIOReturnError` otherwise. +/// @note This function provides a elegant way to enqueue multiple commands and handle errors. +/// +IOReturn RealtekPCIeCardReaderController::enqueueWriteRegisterCommands(const ChipRegValuePairs& pairs) +{ + IOItemCount count = pairs.size(); + + for (auto index = 0; index < count; index += 1) + { + ChipRegValuePair pair = pairs.get(index); + + auto retVal = this->enqueueWriteRegisterCommand(pair.address, pair.mask, pair.value); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue the command at index %d. Error = 0x%x.", index, retVal); + + return retVal; + } + } + + return kIOReturnSuccess; +} + +/// +/// Transfer a sequence of commands to read registers conveniently +/// +/// @param pairs A sequence of pairs of register address and value +/// @param timeout Specify the amount of time in milliseconds +/// @return `kIOReturnSuccess` on success, `kIOReturnError` otherwise. +/// @note This function provides a elegant way to start a command transfer session and handle errors. +/// Same as calling `startCommandTransfer`, a sequence of `enqueueReadRegisterCommand` and `endCommandTransfer`. +/// +IOReturn RealtekPCIeCardReaderController::transferReadRegisterCommands(const ChipRegValuePairs& pairs, UInt32 timeout) +{ + IOReturn retVal; + + // Guard: Begin command transfer + retVal = this->beginCommandTransfer(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate a new command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Enqueue commands + retVal = this->enqueueReadRegisterCommands(pairs); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue the given sequence of commands. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Transfer commands + return this->endCommandTransfer(timeout); +} + +/// +/// Transfer a sequence of commands to write registers conveniently +/// +/// @param pairs A sequence of pairs of register address and value +/// @param timeout Specify the amount of time in milliseconds +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note This function provides a elegant way to start a command transfer session and handle errors. +/// Same as calling `startCommandTransfer`, a sequence of `enqueueWriteRegisterCommand` and `endCommandTransfer`. +/// + +IOReturn RealtekPCIeCardReaderController::transferWriteRegisterCommands(const ChipRegValuePairs& pairs, UInt32 timeout) +{ + IOReturn retVal; + + // Guard: Begin command transfer + retVal = this->beginCommandTransfer(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate a new command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Enqueue commands + retVal = this->enqueueWriteRegisterCommands(pairs); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue the given sequence of commands. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Transfer commands + return this->endCommandTransfer(timeout); +} + +/// +/// Tell the device to stop executing commands +/// +/// @note Port: This function replaces `rtsx_pci_stop_cmd()` defined in `rtsx_psr.c`. +/// @note Subclasses may override this function to inject additional code before calling the generic routine. +/// +IOReturn RealtekPCIeCardReaderController::stopTransfer() +{ + pinfo("Stopping the transfer..."); + + using namespace RTSX::MMIO; + + this->writeRegister32(rHCBCTLR, HCBCTLR::kStopCommand); + + this->writeRegister32(rHDBCTLR, HDBCTLR::kStopDMA); + + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + // Reset DMA + { DMA::rCTL, DMA::CTL::kResetMask, DMA::CTL::kResetValue }, + + // Flush the ring buffer + { RBUF::rCTL, RBUF::CTL::kFlushMask, RBUF::CTL::kFlushValue } + }; + + return this->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +// +// MARK: - Host Data Management +// + +/// +/// [Helper] Convert a physical scatter/gather entry to a value to be stored in the host data buffer +/// +/// @param segment An entry in the scatter/gather list +/// @return The value to be written to the host data buffer. +/// @note Port: This helper function is refactored from `rtsx_pci_add_sg_tbl()` defined in `rtsx_psr.c`. +/// RTS5261 and RTS5208 controllers must override this function but should not include the `option` part in the returned value. +/// +UInt64 RealtekPCIeCardReaderController::transformIOVMSegment(IODMACommand::Segment32 segment) +{ + return static_cast(segment.fIOVMAddr) << 32 | static_cast(segment.fLength) << 12; +} + +/// +/// [Helper] Generate a physical scather/gather list from the given DMA command and enqueue all entries into the host data buffer +/// +/// @param command A non-null, perpared DMA command +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This helper function replaces `rtsx_pci_add_sg_tbl()` defined in `rtsx_psr.c`. +/// @warning The caller must ensure that the given instance of `IODMACommand` is prepared. +/// +IOReturn RealtekPCIeCardReaderController::enqueueDMACommand(IODMACommand* command) +{ + using namespace RTSX::MMIO; + + // + // Step 1: Generate a physical scather/gather list + // + // The maximum number of entries is 384 + // FIXME: Is it OK to assume that the offset to the memory descriptor is 0? (YES, IT's OK; The upper layer can guarantee this) + // FIXME: Pass additional information about the DMA command: e.g., preferred number of segments to be generated (Yes, host device caps) + pinfo("Generating a scather/gather list from the given DMA command..."); + + UInt64 offset = 0; + + IODMACommand::Segment32 segments[256] = {}; + + UInt32 numSegments = 256; // TODO: THIS IS THE DEVICE LIMIT (DECLARE DMA CAPS) + + IOReturn retVal = command->gen32IOVMSegments(&offset, segments, &numSegments); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to generate a scather/gather list from the given DMA command. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Generated a scather/gather list from the given DMA command. Offset = %llu; Number of entries = %d.", offset, numSegments); + + // + // Step 2: Enqueue each scather/gather list entry + // + // Ensure that we can do a trick here to reuse the buffer + static_assert(sizeof(IODMACommand::Segment32) == sizeof(UInt64), + "ABI Error: IODMACommand::Segment32 is no longer 64 bytes long."); + + for (auto index = 0; index < numSegments; index += 1) + { + UInt64 entry = this->transformIOVMSegment(segments[index]) | HDBAR::kSGOptionValid | HDBAR::kSGOptionTransferData; + + pinfo("[%03d] DMA Bus Address = 0x%08x; Length = %d; Entry = 0x%16llx.", + index, segments[index].fIOVMAddr, segments[index].fLength, entry); + + reinterpret_cast(segments)[index] = entry; + } + + // The last entry must have the end bit set + reinterpret_cast(segments)[numSegments - 1] |= static_cast(HDBAR::kSGOptionEnd); + + return this->writeHostDataBuffer(0, segments, numSegments * sizeof(UInt64)); +} + +/// +/// [Helper] Perform a DMA transfer +/// +/// @param command A non-null, perpared DMA command +/// @param timeout Specify the amount of time in milliseconds +/// @param control Specify the value that will be written to the register `HDBCTLR` to customize the DMA transfer +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_dma_transfer()` defined in `rtsx_psr.c`. +/// @note This helper function is invoked by both `performDMARead()` and `performDMAWrite()`. +/// The caller should avoid calling this function directly. +/// @warning The caller must ensure that the given instance of `IODMACommand` is prepared. +/// +IOReturn RealtekPCIeCardReaderController::performDMATransfer(IODMACommand* command, UInt32 timeout, UInt32 control) +{ + // Generate the scather/gather list from the given command + // Write all entries in the list to the host data buffer + pinfo("Processing the DMA command..."); + + IOReturn retVal = this->enqueueDMACommand(command); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to write the scather/gather list generated from the given DMA command to the host data buffer. Error = 0x%x.", retVal); + + return retVal; + } + + // Tell the card reader where to find the data and start the DMA transfer + // The transfer routine will run in a gated context + // Returns `kIOReturnTimeout` if the transfer has timed out; + // `kIOReturnSuccess` if the transfer has succeeded; + // `kIOReturnError` otherwise. + auto action = [](OSObject* controller, void* timeout, void* control, void*, void*) -> IOReturn + { + // Retrieve the controller instance + auto instance = OSDynamicCast(RealtekPCIeCardReaderController, controller); + + passert(instance != nullptr, "The controller instance is invalid."); + + // Tell the device the location of the data buffer and to start the DMA transfer + using namespace RTSX::MMIO; + + instance->hostBufferTransferStatus = kIOReturnNotReady; + + instance->writeRegister32(rHDBAR, instance->hostBufferAddress + RealtekPCIeCardReaderController::kHostDatabufferOffset); + + instance->writeRegister32(rHDBCTLR, *reinterpret_cast(control)); + + // Set up the timer + passert(instance->hostBufferTimer != nullptr, "The host buffer timer should not be NULL."); + + passert(instance->hostBufferTimer->setTimeoutMS(*reinterpret_cast(timeout)) == kIOReturnSuccess, "Should be able to set the timeout."); + + // Wait for the transfer result + // Block the current thread and release the gate + // Either the timeout handler or the interrupt handler will modify the status and wakeup the current thread + instance->commandGate->commandSleep(&instance->hostBufferTransferStatus); + + // When the sleep function returns, the transfer is done + return instance->hostBufferTransferStatus; + }; + + pinfo("Initiating the DMA transfer with timeout = %d ms and control = 0x%08x...", timeout, control); + + retVal = this->commandGate->runAction(action, &timeout, &control); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to perform the DMA transfer. Error = 0x%x.", retVal); + + // TODO: INCREMENT DMA ERROR COUNT + psoftassert(this->stopTransfer() == kIOReturnSuccess, + "Failed to terminate the current transfer session."); + + return retVal; + } + + pinfo("The DMA transfer has completed."); + + return retVal; +} + +/// +/// Perform a DMA read operation +/// +/// @param command A non-null, perpared DMA command +/// @param timeout Specify the amount of time in milliseconds +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// +IOReturn RealtekPCIeCardReaderController::performDMARead(IODMACommand* command, UInt32 timeout) +{ + using namespace RTSX::MMIO; + + pinfo("The host device requests a DMA read operation."); + + return this->performDMATransfer(command, timeout, HDBCTLR::kDMARead | HDBCTLR::kStartDMA | HDBCTLR::kUseADMA); +} + +/// +/// Perform a DMA write operation +/// +/// @param command A non-null, perpared DMA command +/// @param timeout Specify the amount of time in milliseconds +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// +IOReturn RealtekPCIeCardReaderController::performDMAWrite(IODMACommand* command, UInt32 timeout) +{ + using namespace RTSX::MMIO; + + pinfo("The host device requests a DMA write operation."); + + return this->performDMATransfer(command, timeout, HDBCTLR::kStartDMA | HDBCTLR::kUseADMA); +} + +// +// MARK: - Card Power Management +// + +/// +/// Set the driving parameters for the given output voltage +/// +/// @param outputVoltage The target output voltage +/// @param intermediate Pass `false` to begin and complete a new session to set up the driving; +/// Pass `true` to enqueue commands that are needed to set up the driving only. +/// @param timeout Specify the amount of time in milliseconds if `intermediate` is set to `false`. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `*_fill_driving()` defined in each concrete controller file. +/// +IOReturn RealtekPCIeCardReaderController::setDrivingForOutputVoltage(OutputVoltage outputVoltage, bool intermediate, UInt32 timeout) +{ + // Retrieve the driving table and the selector + const DrivingTable* table; + + UInt8 selector; + + if (outputVoltage == OutputVoltage::k3d3V) + { + table = this->parameters.sd30DriveTable3d3V; + + selector = this->parameters.sd30DriveSelector3d3V; + } + else + { + table = this->parameters.sd30DriveTable1d8V; + + selector = this->parameters.sd30DriveSelector1d8V; + } + + // Guard: Check whether the controller needs to set up the driving + if (table == nullptr) + { + pwarning("The controller does not need to set up the driving."); + + return kIOReturnSuccess; + } + + // Populate the registers and their values + using namespace RTSX::Chip::CARD::SD30::DRVSEL; + + const ChipRegValuePair pairs[] = + { + { rCLK, 0xFF, table->entries[selector].clock }, + { rCMD, 0xFF, table->entries[selector].command }, + { rDAT, 0xFF, table->entries[selector].data } + }; + + // Guard: Check whether this is an intermediate operation + if (intermediate) + { + return this->enqueueWriteRegisterCommands(SimpleRegValuePairs(pairs)); + } + else + { + return this->transferWriteRegisterCommands(SimpleRegValuePairs(pairs), timeout); + } +} + +// +// MARK: - Clock Configurations +// + +/// +/// Adjust the card clock if DMA transfer errors occurred +/// +/// @param cardClock The current card clock +/// @return The adjusted card clock. +/// @note Port: This function replaces the code block that reduces the card clock in `rtsx_pci_switch_clock()` defined in `rtsx_psr.c`. +/// By default, this function does not adjust the card clock and return the given clock. +/// RTS5227 controller must override this function. +/// +UInt32 RealtekPCIeCardReaderController::getAdjustedCardClockOnDMAError(UInt32 cardClock) +{ + return cardClock; +} + +/// +/// Convert the given SSC clock to the divider N +/// +/// @param clock The SSC clock in MHz +/// @return The divider N. +/// @note Port: This function replaces `conv_clk_and_div_n()` defined in `rtsx_psr.c`. +/// RTL8411 series controllers must override this function. +/// +UInt32 RealtekPCIeCardReaderController::sscClock2DividerN(UInt32 clock) +{ + return clock - 2; +} + +/// +/// Convert the given divider N back to the SSC clock +/// +/// @param n The divider N +/// @return The SSC clock in MHz. +/// @note Port: This function replaces `conv_clk_and_div_n()` defined in `rtsx_psr.c`. +/// RTL8411 series controllers must override this function. +/// +UInt32 RealtekPCIeCardReaderController::dividerN2SSCClock(UInt32 n) +{ + return n + 2; +} + +/// +/// Switch to the given card clock +/// +/// @param cardClock The card clock in Hz +/// @param sscDepth The SSC depth value +/// @param initialMode Pass `true` if the card is at its initial stage +/// @param doubleClock Pass `true` if the SSC clock should be doubled +/// @param vpclock Pass `true` if VPCLOCK is used +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_switch_clock()` defined in `rtsx_psr.c`. +/// @note Port: RTS5261 and RTS5228 controllers must override this function. +/// +IOReturn RealtekPCIeCardReaderController::switchCardClock(UInt32 cardClock, SSCDepth sscDepth, bool initialMode, bool doubleClock, bool vpclock) +{ + using namespace RTSX::Chip; + + // Adjust the clock divider and the frequency if the card is at its initial stage + UInt8 clockDivider = SD::CFG1::kClockDivider0; + + if (initialMode) + { + clockDivider = SD::CFG1::kClockDivider128; + + cardClock = MHz2Hz(30); + } + + // Set the clock divider + IOReturn retVal = this->writeChipRegister(SD::rCFG1, SD::CFG1::kClockDividerMask, clockDivider); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the clock divider to %d. Error = 0x%x.", clockDivider, retVal); + + return retVal; + } + + // Get the new card clock if DMA transfer errors have occurred + cardClock = this->getAdjustedCardClockOnDMAError(cardClock); + + cardClock = Hz2MHz(cardClock); + + pinfo("The card clock will be switched to %d MHz.", cardClock); + + // Check whether the host should double the SSC clock + UInt32 sscClock = cardClock; + + if (doubleClock && !initialMode) + { + sscClock *= 2; + } + + // Check whether the host should switch the clock + pinfo("Internal SSC Clock = %d MHz; Current Clock = %d MHz.", sscClock, this->currentSSCClock); + + if (sscClock == this->currentSSCClock) + { + pinfo("No need to switch the clock."); + + return kIOReturnSuccess; + } + + // Calculate the SSC clock divider N + UInt32 n = this->sscClock2DividerN(sscClock); + + if (sscClock <= 2 || n > RealtekPCIeCardReaderController::kMaxSSCClockDividerN) + { + perr("The SSC clock divider N %d derived from the clock %d MHz is invalid.", n, sscClock); + + return kIOReturnInvalid; + } + + // Calculate the MCU count + UInt32 mcus = 125 / sscClock + 3; + + pinfo("MCU Count = %d.", mcus); + + if (mcus > 15) + { + mcus = 15; + } + + // Ensure that the SSC clock divider N is not too small + UInt8 divider; + + for (divider = CLK::DIV::k1; divider < CLK::DIV::k8; divider += 1) + { + if (n >= RealtekPCIeCardReaderController::kMinSSCClockDividerN) + { + break; + } + + n = this->sscClock2DividerN(this->dividerN2SSCClock(n) * 2); + } + + pinfo("SSC clock divider N = %d; Divider register value = %d.", n, divider); + + // Calculate the SSC depth + static constexpr UInt8 kSSCDepthTable[] = + { + [static_cast(SSCDepth::k4M)] = SSC::CTL2::kDepth4M, + [static_cast(SSCDepth::k2M)] = SSC::CTL2::kDepth2M, + [static_cast(SSCDepth::k1M)] = SSC::CTL2::kDepth1M, + [static_cast(SSCDepth::k500K)] = SSC::CTL2::kDepth500K, + [static_cast(SSCDepth::k250K)] = SSC::CTL2::kDepth250K, + }; + + UInt8 sscDepthRegValue = kSSCDepthTable[static_cast(sscDepth)]; + + pinfo("SSC Depth RegVal = %d.", sscDepthRegValue); + + // Double the SSC depth if necessary + if (doubleClock) + { + sscDepthRegValue = SSC::CTL2::doubleDepth(sscDepthRegValue); + } + + // Revise the SSC depth + if (divider > CLK::DIV::k1) + { + if (sscDepthRegValue > (divider - 1)) + { + sscDepthRegValue -= (divider - 1); + } + else + { + sscDepthRegValue = SSC::CTL2::kDepth4M; + } + } + + pinfo("Revised SSC Depth RegVal = %d.", sscDepthRegValue); + + // Send the command + const ChipRegValuePair pairs[] = + { + { CLK::rCTL, CLK::CTL::kLowFrequency, CLK::CTL::kLowFrequency }, + { CLK::rDIV, 0xFF, static_cast(divider << 4 | mcus) }, + { SSC::rCTL1, SSC::CTL1::kRSTB, 0 }, + { SSC::rCTL2, SSC::CTL2::kDepthMask, sscDepthRegValue }, + { SSC::rDIVN0, 0xFF, static_cast(n) }, + { SSC::rCTL1, SSC::CTL1::kRSTB, SSC::CTL1::kRSTB }, + + // Send the following commands if vpclock is true + { SD::rVPCLK0CTL, SD::VPCTL::kPhaseNotReset, 0 }, + { SD::rVPCLK0CTL, SD::VPCTL::kPhaseNotReset, SD::VPCTL::kPhaseNotReset } + }; + + if (vpclock) + { + retVal = this->transferWriteRegisterCommands(SimpleRegValuePairs(pairs), 2000); + } + else + { + retVal = this->transferWriteRegisterCommands(SimpleRegValuePairs(pairs, arrsize(pairs) - 2), 2000); + } + + if (retVal != kIOReturnSuccess) + { + perr("Failed to transfer commands to set the card clock. Error = 0x%x.", retVal); + + return retVal; + } + + // Wait until the SSC clock becomes stable + IODelay(RealtekPCIeCardReaderController::kWaitStableSSCClock); + + retVal = this->writeChipRegister(CLK::rCTL, CLK::CTL::kLowFrequency, 0); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to toggle the clock. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("SSC clock has been switched to %d MHz.", sscClock); + + this->currentSSCClock = sscClock; + + return kIOReturnSuccess; +} + +// +// MARK: - Card Detection and Write Protection +// + +/// +/// Check whether the card has write protection enabled +/// +/// @return `true` if the card is write protected, `false` otherwise. +/// +bool RealtekPCIeCardReaderController::isCardWriteProtected() +{ + using namespace RTSX::MMIO; + + return BitOptions(this->readRegister32(rBIPR)).contains(BIPR::kSDWriteProtected); +} + +/// +/// Check whether a card is present +/// +/// @return `true` if a card exists, `false` otherwise. +/// @note Port: This function replaces `rtsx_pci_card_exist()` and `cd_deglitch()` defined in `rtsx_psr.c`. +/// @warning: This function supports SD cards only. +/// +bool RealtekPCIeCardReaderController::isCardPresent() +{ + using namespace RTSX::MMIO; + + return BitOptions(this->readRegister32(rBIPR)).contains(BIPR::kSDExists); +} + +// +// MARK: - Overcurrent Protection Support +// + +/// +/// Initialize and enable overcurrent protection +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_init_ocp()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::initOvercurrentProtection() +{ + pinfo("Initializing the overcurrent protection..."); + + if (!this->parameters.ocp.enable) + { + pinfo("The device specifies not to enable overcurrent protection."); + + return kIOReturnSuccess; + } + + // Setup overcurrent protection registers + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + // Power up the OCP unit + { rFPDCTL, FPDCTL::kOCPPowerMask, FPDCTL::kOCPPowerUpValue }, + + // Setup the time + { OCP::rPARA1, OCP::PARA1::kSDTimeMask, OCP::PARA1::kSDTimeValue800 }, + + // Setup the total harmonic distortion value + { OCP::rPARA2, OCP::PARA2::kSDThdMask, this->parameters.ocp.sdTHD800mA }, + + // Setup the glitch value + { OCP::rGLITCH, OCP::GLITCH::kSDMask, this->parameters.ocp.sdGlitch } + }; + + return this->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +/// +/// Enable overcurrent protection +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_enable_ocp()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::enableOvercurrentProtection() +{ + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + // Ensure that OCP power is up + { rFPDCTL, FPDCTL::kOCPPowerMask, FPDCTL::kOCPPowerUpValue }, + + // Enable OCP detection and hardware interrupts + { OCP::rCTL, OCP::CTL::kSDDetectionAndInterruptMask, OCP::CTL::kSDEnableDetectionAndInterruptValue } + }; + + return this->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +/// +/// Disable overcurrent protection +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_disable_ocp()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::disableOvercurrentProtection() +{ + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + // Disable OCP detection and hardware interrupts + { OCP::rCTL, OCP::CTL::kSDDetectionAndInterruptMask, OCP::CTL::kSDDisableDetectionAndInterruptValue }, + + // Power down the OCP unit + { rFPDCTL, FPDCTL::kOCPPowerMask, FPDCTL::kOCPPowerDownValue } + }; + + return this->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +/// +/// Get the overcurrent protection status +/// +/// @param status The status on return +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_get_ocpstat()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::getOvercurrentProtectionStatus(UInt8& status) +{ + using namespace RTSX::Chip; + + pinfo("Fetching the current overcurrent protection status..."); + + return this->readChipRegister(OCP::rSTAT, status); +} + +/// +/// Clear the overcurrent protection status +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_clear_ocpstat()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::clearOvercurrentProtectionStatus() +{ + using namespace RTSX::Chip; + + UInt8 mask = OCP::CTL::kSDClearStatMask | OCP::CTL::kSDClearInterruptMask; + + UInt8 value = OCP::CTL::kSDClearStatValue | OCP::CTL::kSDClearInterruptValue; + + IOReturn retVal = this->writeChipRegister(OCP::rCTL, mask, value); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to clear the OCP status (step 1). Error = 0x%x.", retVal); + + return retVal; + } + + IODelay(100); + + retVal = this->writeChipRegister(OCP::rCTL, mask, 0); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to clear the OCP status (step 2). Error = 0x%x.", retVal); + } + + return retVal; +} + +// +// MARK: - Card Pull Control Management +// + + +/// +/// Enable pull control for the SD card +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_card_pull_ctl_enable()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::enablePullControlForSDCard() +{ + return this->transferWriteRegisterCommands(*this->parameters.sdEnablePullControlTable); +} + +/// +/// Disable pull control for the SD card +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_card_pull_ctl_disable()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::disablePullControlForSDCard() +{ + return this->transferWriteRegisterCommands(*this->parameters.sdDisablePullControlTable); +} + +// +// MARK: - OOBS Polling +// + +/// +/// Check whether the driver needs to modify the PHY::RCR0 register to enable or disable OOBS polling +/// +/// @return `true` if the controller is not RTS525A nor RTS5260. +/// @note RTS525A and RTS5260 controllers should override this function and return `false`. +/// +bool RealtekPCIeCardReaderController::oobsPollingRequiresPhyRCR0Access() +{ + return true; +} + +/// +/// Check whether the hardware supports OOBS polling +/// +/// @return `true` if supported, `false` otherwise. +/// @note By default, this function returns `false`. +/// @note e.g., RTS524A and RTS525A should override this function and return `true`. +/// +bool RealtekPCIeCardReaderController::supportsOOBSPolling() +{ + return false; +} + +/// +/// Enable OOBS polling +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn RealtekPCIeCardReaderController::enableOOBSPolling() +{ + // Check whether RCR0 needs to be modified to enable OOBS polling + if (this->oobsPollingRequiresPhyRCR0Access()) + { + using namespace RTSX::PHYS; + + IOReturn retVal = this->setPhysRegisterBitAtIndex(rRCR0, RCR0::kOOBSControlBitIndex); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the OOBS bit in the PHY::RCR0 register. Error = 0x%x.", retVal); + + return retVal; + } + } + + // Configure chip registers to enable OOBS polling + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + { OOBS::rOFFTIMER, 0x32 }, + { OOBS::rONTIMER, 0x05 }, + { OOBS::rVCMONTIMER, 0x83 }, + { OOBS::rPOLLING, 0xDE }, + }; + + return this->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +/// +/// Disable OOBS polling +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn RealtekPCIeCardReaderController::disableOOBSPolling() +{ + if (this->oobsPollingRequiresPhyRCR0Access()) + { + using namespace RTSX::PHYS; + + IOReturn retVal = this->clearPhysRegisterBitAtIndex(rRCR0, RCR0::kOOBSControlBitIndex); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to clear the OOBS bit in the PHY::RCR0 register. Error = 0x%x.", retVal); + + return retVal; + } + } + + // Configure chip registers to disable OOBS polling + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + { OOBS::rVCMONTIMER, 0x03 }, + { OOBS::rPOLLING, 0x00 }, + }; + + return this->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +// +// MARK: - Ping Pong Buffer +// + +/// +/// Read from the ping pong buffer +/// +/// @param destination The buffer to store bytes +/// @param length The number of bytes to read (cannot exceed 512 bytes) +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_read_ppbuf()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::readPingPongBuffer(UInt8* destination, IOByteCount length) +{ + pinfo("Request to read %llu bytes from the ping pong buffer.", length); + + // Guard: The ping pong buffer is 512 bytes long + if (length > 512) + { + perr("The number of bytes requested to read should not exceed 512."); + + return kIOReturnBadArgument; + } + + // Guard: Check the destination buffer + if (destination == nullptr) + { + perr("The destination buffer cannot be NULL."); + + return kIOReturnBadArgument; + } + + // Size of the command queue is 256 + // Divide into multiple transfer sessions + for (auto index = 0; index < length; index += 256) + { + // Calculate the length for this transfer session + IOItemCount newLength = static_cast(min(length - index, 256)); + + // Generate a sequence of register addresses + ContiguousRegValuePairsForReadAccess pairs(RTSX::Chip::PPBUF::rBASE2 + index, newLength); + + // Guard: Read the current set of registers + IOReturn retVal = this->transferReadRegisterCommands(pairs, 250); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to transfer commands to read %u bytes. Error = 0x%x.", newLength, retVal); + + return retVal; + } + + // Copy the register values from the command buffer to the destination buffer + retVal = this->readHostBuffer(index, destination + index, newLength); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to transfer %d bytes from the command buffer to the destination buffer. Error = 0x%x.", newLength, retVal); + + return kIOReturnError; + } + } + + pinfo("%llu bytes have been read from the ping pong buffer.", length); + + return kIOReturnSuccess; +} + +/// +/// Write to the ping pong buffer +/// +/// @param source The buffer to write +/// @param length The number of bytes to write (cannot exceed 512 bytes) +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_write_ppbuf()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::writePingPongBuffer(const UInt8* source, IOByteCount length) +{ + pinfo("Request to write %llu bytes from the ping pong buffer.", length); + + // Guard: The ping pong buffer is 512 bytes long + if (length > 512) + { + perr("The number of bytes requested to read should not exceed 512."); + + return kIOReturnBadArgument; + } + + // Guard: Check the source buffer + if (source == nullptr) + { + perr("The source buffer cannot be NULL."); + + return kIOReturnBadArgument; + } + + // Size of the command queue is 256 + // Divide into multiple transfer sessions + for (auto index = 0; index < length; index += 256) + { + // Calculate the length for this transfer session + IOItemCount newLength = static_cast(min(length - index, 256)); + + // Generate a sequence of register addresses and their values + ContiguousRegValuePairsForWriteAccess pairs(RTSX::Chip::PPBUF::rBASE2 + index, newLength, source + index); + + // Guard: Read the current set of registers + IOReturn retVal = this->transferWriteRegisterCommands(pairs, 250); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to transfer commands to read %u bytes. Error = 0x%x.", newLength, retVal); + + return retVal; + } + } + + pinfo("%llu bytes have been written to the ping pong buffer.", length); + + return kIOReturnSuccess; +} + +// +// MARK: - Rx/Tx Phases +// + +/// +/// Get the clock phase for Rx +/// +/// @return The clock phase. +/// +ClockPhase RealtekPCIeCardReaderController::getRxClockPhase() +{ + return this->parameters.initialRxClockPhase; +} + +/// +/// Get the clock phase for Tx +/// +/// @return The clock phase. +/// +ClockPhase RealtekPCIeCardReaderController::getTxClockPhase() +{ + return this->parameters.initialTxClockPhase; +} + +// +// MARK: - Active State Power Management +// + +/// +/// Toggle the active state power management +/// +/// @param enable `true` if ASPM should be enabled, `false` otherwise. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_comm_set_aspm()` and `ops->set_aspm()` defined in `rtsx_pcr.c`. +/// +IOReturn RealtekPCIeCardReaderController::setASPM(bool enable) +{ + using namespace RTSX::Chip; + + if (this->parameters.pm.isASPMEnabled == enable) + { + pinfo("No need to set the ASPM state."); + + return kIOReturnSuccess; + } + + UInt8 regVal = AFCTL::kCTL0 | AFCTL::kCTL1; + + if (this->parameters.pm.isASPML1Enabled && enable) + { + regVal = 0; + } + + IOReturn retVal = this->writeChipRegister(rAFCTL, AFCTL::kCTL0 | AFCTL::kCTL1, regVal); + + if (retVal == kIOReturnSuccess) + { + this->parameters.pm.isASPMEnabled = enable; + } + + if (this->parameters.pm.isASPMEnabled && !enable) + { + IOSleep(10); + } + + return retVal; +} + +/// +/// Set the L1 substates configuration +/// +/// @param active Pass `true` to set the active state +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_set_l1off_sub_cfg_d0()` and `set_l1off_cfg_sub_d0()` defined in `rtsx_pcr.c`. +/// @note The base controller provides a default implementation that simply returns `kIOReturnSuccess`. +/// +IOReturn RealtekPCIeCardReaderController::setL1OffSubConfigD0(bool active) +{ + return kIOReturnSuccess; +} + +/// +/// Set the LTR latency +/// +/// @param latency The latency +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_comm_set_ltr_latency()` and `rtsx_set_ltr_latency()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::setLTRLatency(UInt32 latency) +{ + using namespace RTSX::Chip; + + pinfo("Setting tht LTR latency to %d.", latency); + + const ChipRegValuePair pairs[] = + { + // Set the latency value + { MSG::TX::rDATA0, 0xFF, static_cast(latency & 0xFF) }, + { MSG::TX::rDATA1, 0xFF, static_cast(latency >> 8) }, + { MSG::TX::rDATA2, 0xFF, static_cast(latency >> 16) }, + { MSG::TX::rDATA3, 0xFF, static_cast(latency >> 24) }, + + // Enable Tx and select the latency mode + { LTR::rCTL, LTR::CTL::kTxMask | LTR::CTL::kLatencyModeMask, LTR::CTL::kEnableTx | LTR::CTL::kLatencyModeSoftware } + }; + + return this->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +/// +/// Notify the card reader to enter into the worker state +/// +/// @note Port: This function replaces `rtsx_pci_start_run()`, `rtsx_pm_full_on()` and `rtsx_comm_pm_full_on()` defined in `rtsx_psr.c`. +/// +void RealtekPCIeCardReaderController::enterWorkerState() +{ + if (!this->isIdle) + { + return; + } + + pinfo("Entering the worker state..."); + + this->isIdle = false; + + //psoftassert(this->enableLEDBlinking() == kIOReturnSuccess, "Failed to enable the LED."); + + psoftassert(this->disableASPM() == kIOReturnSuccess, "Failed to disable the ASPM."); + + IOSleep(1); // Fix the DMA transfer timeout issue after disabling ASPM on RTS5260 + + if (this->parameters.pm.isLTRModeEnabled) + { + pinfo("LTR mode is currently enabled. Will adjust the LTR latency."); + + psoftassert(this->setLTRLatency(this->parameters.pm.ltrActiveLatency) == kIOReturnSuccess, "Failed to adjust the LTR latency."); + } + + if (this->parameters.pm.enableLTRL1SSPowerGate) + { + psoftassert(this->setL1OffSubConfigD0(true) == kIOReturnSuccess, "Failed to set the active L1 substate."); + } +} + +// +// MARK: - Hardware Interrupt Management +// + +/// +/// A filter that examines the interrupt event source +/// +/// @param source The interrupt event source +/// @return `true` if the interrupt is of interest, `false` otherwise. +/// @note The filter runs in the primary interrupt context. +/// +bool RealtekPCIeCardReaderController::interruptEventSourceFilter(IOFilterInterruptEventSource* source) +{ + // Retrieve and examine pending interrupts + using namespace RTSX::MMIO; + + UInt32 pendingInterrupts = this->readRegister32(rBIPR); + + UInt32 enabledInterrupts = this->readRegister32(rBIER); + + return (pendingInterrupts & enabledInterrupts) != 0; +} + +/// +/// Interrupt service routine +/// +/// @param sender The interrupt event source +/// @param count The number of interrupts seen before delivery +/// @note The interrupt service routine runs in a gated context. +/// It checks hardware registers to identify the reason and invoke helper handlers. +/// @note The interrupt event source filter guarantees that there is a pending interrupt when the handler is invoked. +/// +void RealtekPCIeCardReaderController::interruptHandlerGated(IOInterruptEventSource* sender, int count) +{ + // Retrieve pending interrupts + using namespace RTSX::MMIO; + + pinfo("Interrupt handler started."); + + BitOptions pendingInterrupts = this->readRegister32(rBIPR); + + // Acknowledge the interrupt + this->writeRegister32(rBIPR, pendingInterrupts.flatten()); + + pinfo("Interrupts are ackownledged."); + + pinfo("Pending interrupts = 0x%x.", pendingInterrupts.flatten()); + + // Check whether the interrupt is already handled + if (pendingInterrupts.flatten() == 0xFFFFFFFF) + { + pinfo("Interrupts are already handled."); + + return; + } + + // Filter out disabled interrupts but keep the low 23 bits + pendingInterrupts.mutativeBitwiseAnd(this->readRegister32(rBIER) | 0x7FFFFF); + + pinfo("Filtered pending interrupts = 0x%x.", pendingInterrupts.flatten()); + + // Examine pending interrupts + // Case 1: SD Overcurrent Protection Interrupts + if (pendingInterrupts.contains(BIPR::kSDOvercurrentOccurred) && this->parameters.ocp.enable) + { + this->onSDCardOvercurrentOccurredGated(); + } + + // Case 2: Host Command/Data Transfer Interrupts + if (pendingInterrupts.contains(BIPR::kTransferSucceeded)) + { + this->onTransferDoneGated(true); + } + + if (pendingInterrupts.contains(BIPR::kTransferFailed)) + { + this->onTransferDoneGated(false); + } + + // Case 3: Card Insertion/Removal Interrupts + if (pendingInterrupts.contains(BIPR::kSD)) + { + if (pendingInterrupts.contains(BIPR::kSDExists)) + { + this->onSDCardInsertedGated(); + } + else + { + this->onSDCardRemovedGated(); + } + } +} + +/// +/// Timeout interrupt service routine +/// +/// @param sender The timer event source +/// @note The timeout handler runs in a gated context. +/// +void RealtekPCIeCardReaderController::bufferTransferTimeoutHandlerGated(IOTimerEventSource* sender) +{ + // The transfer has timed out + pinfo("The current transfer session has timed out."); + + pinfo("BIER = 0x%08x.", this->readRegister32(RTSX::MMIO::rBIER)); + + pinfo("BIPR = 0x%08x.", this->readRegister32(RTSX::MMIO::rBIPR)); + + // Update the transfer status + this->hostBufferTransferStatus = kIOReturnTimeout; + + // Wakeup the client thread + this->commandGate->commandWakeup(&this->hostBufferTransferStatus); +} + +/// +/// Helper interrupt service routine when a host command or data transfer is done +/// +/// @param succeeded `true` if the transfer has succeeded. `false` otherwise. +/// @note This interrupt service routine runs in a gated context. +/// +void RealtekPCIeCardReaderController::onTransferDoneGated(bool succeeded) +{ + // All accesses to the status variable is performed in a gated context + // Guard: Check whether the transfer has already timed out + if (this->hostBufferTransferStatus == kIOReturnTimeout) + { + pinfo("The current transfer session has already timed out."); + + return; + } + + pinfo("The current transfer session has completed. Succeeded = %s.", YESNO(succeeded)); + + // The transfer has not yet timed out + // Cancel the timer but don't remove it from the work loop and release it + // We will reuse the timer later + this->hostBufferTimer->cancelTimeout(); + + // Update the transfer status + this->hostBufferTransferStatus = succeeded ? kIOReturnSuccess : kIOReturnError; + + // Wakeup the client thread + this->commandGate->commandWakeup(&this->hostBufferTransferStatus); +} + +/// +/// Helper interrupt service routine when an overcurrent is detected +/// +/// @note This interrupt service routine runs in a gated context. +/// +void RealtekPCIeCardReaderController::onSDCardOvercurrentOccurredGated() +{ + // Guard: Retrieve the current OCP status + using namespace RTSX::Chip; + + pinfo("Detected an overcurrent."); + + UInt8 status = 0; + + if (this->getOvercurrentProtectionStatus(status) != kIOReturnSuccess) + { + perr("Failed to retrieve the current OCP status."); + + return; + } + + // Guard: Check the current OCP status + if (!BitOptions(status).contains(OCP::STAT::kSDNow | OCP::STAT::kSDEver)) + { + pinfo("The current OCP status 0x%x shows that no overcurrent event detected on SD.", status); + + return; + } + + // Overcurrent Detected + psoftassert(this->powerOffCard() == kIOReturnSuccess, + "Failed to power off the card."); + + psoftassert(this->writeChipRegister(CARD::rOUTPUT, CARD::OUTPUT::kSDMask, CARD::OUTPUT::kDisableSDValue) == kIOReturnSuccess, + "Failed to disable the card output."); + + psoftassert(this->clearOvercurrentProtectionStatus() == kIOReturnSuccess, + "Failed to clear the OCP status."); +} + +/// +/// Helper interrupt service routine when a SD card is inserted +/// +/// @note This interrupt service routine runs in a gated context. +/// @note Port: This function replaces `rtsx_pci_card_detect()` defined in `rtsx_psr.c` but has a completely different design and implementation. +/// +void RealtekPCIeCardReaderController::onSDCardInsertedGated() +{ + // Notify the host device + pinfo("A SD card is inserted."); + + this->slot->onSDCardInsertedGated(); +} + +/// +/// Helper interrupt service routine when a SD card is removed +/// +/// @note This interrupt service routine runs in a gated context. +/// @note Port: This function replaces `rtsx_pci_card_detect()` defined in `rtsx_psr.c` but has a completely different design and implementation. +/// +void RealtekPCIeCardReaderController::onSDCardRemovedGated() +{ + // Notify the host device + pinfo("The SD card has been removed."); + + this->slot->onSDCardRemovedGated(); +} + +// +// MARK: - Hardware Initialization and Configuration +// + +/// +/// Get the IC revision +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `*_get_ic_version()` defined in each controller file. +/// +IOReturn RealtekPCIeCardReaderController::getRevision(Revision& revision) +{ + pinfo("Fetching the chip revision..."); + + using namespace RTSX::Chip; + + UInt8 regVal = 0xFF; + + IOReturn retVal = this->readChipRegister(rDUMMY, regVal); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to read the revision register. Error = 0x%x.", retVal); + + return retVal; + } + + switch (regVal & DUMMY::kICRevisionMask) + { + case 0: + { + revision = Revision::kA; + + pinfo("Chip revision is A."); + + break; + } + + case 1: + { + revision = Revision::kB; + + pinfo("Chip revision is B."); + + break; + } + + case 2: + { + revision = Revision::kC; + + pinfo("Chip revision is C."); + + break; + } + + case 3: + { + revision = Revision::kD; + + pinfo("Chip revision is D."); + + break; + } + + default: + { + pwarning("Undefined revision."); + + revision = Revision::kUnknown; + + break; + } + } + + pinfo("Chip revision has been fetched."); + + return kIOReturnSuccess; +} + +/// +/// Optimize the physical layer +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `optimize_phy()` defined in the the `pcr->ops`. +/// +IOReturn RealtekPCIeCardReaderController::optimizePhys() +{ + return kIOReturnSuccess; +} + +/// +/// Initialize the hardware (Common Part) +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_init_hw()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekPCIeCardReaderController::initHardwareCommon() +{ + pinfo("Initializing the card reader..."); + + // Enable the bus interrupt + using namespace RTSX::MMIO; + + pinfo("Enabling the bus interrupt..."); + + UInt32 bier = BIER::kEnableTransferSuccess | + BIER::kEnableTransferFailure | + BIER::kEnableSD; + + if (this->parameters.ocp.enable) + { + bier |= BIER::kEnableSDOvercurrent; + } + + this->writeRegister32(rBIER, bier); + + pinfo("Bus interrupt has been enabled. MMIO::BIER = 0x%08x.", bier); + + // Power on the SSC + // TODO: Create a virtual function to support RTS5261 + pinfo("Powering on the SSC..."); + + using namespace RTSX::Chip; + + IOReturn retVal = this->writeChipRegister(rFPDCTL, FPDCTL::kSSCPowerMask, FPDCTL::kSSCPowerUpValue); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to power on the SSC. Error = 0x%x.", retVal); + + return retVal; + } + + // Wait until the SSC power becomes stable + IODelay(200); + + pinfo("SSC has been powered on.") + + // Disable the ASPM during the initialization + psoftassert(this->disableASPM() == kIOReturnSuccess, "Failed to disable the ASPM."); + + // Optimize the physical layer + retVal = this->optimizePhys(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to optimize the physical layer. Error = 0x%x.", retVal); + + return retVal; + } + + // Configure the card reader + pinfo("Configuring the card reader chip..."); + + const ChipRegValuePair pairs[] = + { + // Set `mcu_cnt` to 7 to ensure that data can be sampled properly + { CLK::rDIV, 0x07, 0x07 }, + + // Set the host sleep state + { rHSSTA, HSSTA::kMask, HSSTA::kHostWakeup }, + + // Disable the card clock + { CARD::rCLK, CARD::CLK::kMask, CARD::CLK::kDisable }, + + // Reset the link state + { rLINKSTA, 0x0A, 0x00 }, + + // Set the card drive selector + { CARD::rDRVSEL, 0xFF, this->parameters.cardDriveSelector }, + + // Enable the SSC clock + { SSC::rCTL1, 0xFF, SSC::CTL1::kEnable8x | SSC::CTL1::kSelector4M }, + + // Set the SSC depth + // TODO: Create a virtual function to retrieve the value of `SSC Depth 2M` to support RTS5261 and RTS5228 + { SSC::rCTL2, 0xFF, SSC::CTL2::kDepth2M }, + + // Disable the power save mode of the card + { rLINKSTA, 0x16, 0x10 }, + + // Clear the link ready interrupt + { rIRQSTAT0, IRQSTAT0::kLinkReadyInt, IRQSTAT0::kLinkReadyInt }, + + // Enlarge the estimation window of PERST# glitch to reduce the chance of invalid card interrupt + { rPGWIDTH, 0xFF, 0x80 }, + + // Update the RC oscillator to 400k + // Bit[0] F_HIGH: for RC oscillator, Rst_value is 1'b1, 1: 2M, 0: 400k + { rRCCTL, 0x01, 0x00 }, + + // Set interrupt write clear + // Bit 1: U_elbi_if_rd_clr_en + // 1: Enable ELBI interrupt[31:22] & [7:0] flag read clear + // 0: ELBI interrupt flag[31:22] & [7:0] only can be write clear + { rNFTSTXCTL, 0x02, 0x00 }, + }; + + retVal = this->transferWriteRegisterCommands(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to configure the card reader. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The card reader chip has been configured."); + + // Initialize the overcurrent protection + retVal = this->initOvercurrentProtection(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initialize the overcurrent protection. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Overcurrent protection has been initialized."); + + // Enable the clock power management + // Need to write this register if the chip is RTS5250/524A/525A/5260/5261/5228 + // TODO: Create a virtual function `enableClockPowerManagement()` + pinfo("Enabling the clock power management..."); + + retVal = this->writeChipRegister(rPMCLKFCTL, PMCLKFCTL::kEnableClockPM, PMCLKFCTL::kEnableClockPM); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable the clock power management. Error = 0x%x.", retVal); + + return retVal; + } + + // TODO: RELOCATE THIS +#define PCI_EXP_LNKCTL 16 /* Link Control */ +#define PCI_EXP_LNKCTL_ASPMC 0x0003 /* ASPM Control */ +#define PCI_EXP_LNKCTL_ASPM_L0S 0x0001 /* L0s Enable */ +#define PCI_EXP_LNKCTL_ASPM_L1 0x0002 /* L1 Enable */ +#define PCI_EXP_LNKCTL_RCB 0x0008 /* Read Completion Boundary */ +#define PCI_EXP_LNKCTL_LD 0x0010 /* Link Disable */ +#define PCI_EXP_LNKCTL_RL 0x0020 /* Retrain Link */ +#define PCI_EXP_LNKCTL_CCC 0x0040 /* Common Clock Configuration */ +#define PCI_EXP_LNKCTL_ES 0x0080 /* Extended Synch */ +#define PCI_EXP_LNKCTL_CLKREQ_EN 0x0100 /* Enable clkreq */ +#define PCI_EXP_LNKCTL_HAWD 0x0200 /* Hardware Autonomous Width Disable */ +#define PCI_EXP_LNKCTL_LBMIE 0x0400 /* Link Bandwidth Management Interrupt Enable */ +#define PCI_EXP_LNKCTL_LABIE 0x0800 /* Link Autonomous Bandwidth Interrupt Enable */ + + IOPCIeDeviceConfigSet16(this->device, PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_CLKREQ_EN); + + pinfo("Clock power management has been enabled."); + + // Enter L1 when the host is idle + // TODO: CHECK THIS + this->device->configWrite8(0x70F, 0x5B); + + // Perform device-specific initialization + retVal = this->initHardwareExtra(); + + if (retVal != kIOReturnSuccess) + { + perr("The device-specific initialization routine returns an error 0x%x.", retVal); + + return retVal; + } + + // Configure the ASPM + // TODO: CHECK THIS + retVal = this->writeChipRegister(rAFCTL, AFCTL::kCTL0 | AFCTL::kCTL1, AFCTL::kCTL0 | AFCTL::kCTL1); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the ASPM::CTL0 and ASPM::CTL1. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Initialized the card reader..."); + + return kIOReturnSuccess; +} + +// +// MARK: - Startup Routines +// + +/// +/// Map the device memory +/// +/// @return `true` on success, `false` otherwise. +/// +bool RealtekPCIeCardReaderController::mapDeviceMemory() +{ + UInt8 bar = this->getDeviceMemoryMapBaseAddressRegister(); + + pinfo("Prepare to map the device memory (BAR = 0x%02x)...", bar); + + this->deviceMemoryMap = this->device->mapDeviceMemoryWithRegister(bar); + + if (this->deviceMemoryMap == nullptr) + { + perr("Failed to map the device memory."); + + return false; + } + + // The descriptor is valid as long as the controller holds a reference to the device memory map + this->deviceMemoryDescriptor = this->deviceMemoryMap->getMemoryDescriptor(); + + if (this->deviceMemoryDescriptor->prepare() != kIOReturnSuccess) + { + perr("Failed to prepare the device memory."); + + this->deviceMemoryMap->release(); + + this->deviceMemoryMap = nullptr; + + return false; + } + + pinfo("The device memory has been mapped successfully."); + + return true; +} + +/// +/// Setup the workloop +/// +/// @return `true` on success, `false` otherwise. +/// +bool RealtekPCIeCardReaderController::setupWorkLoop() +{ + pinfo("Creating the workloop and the command gate..."); + + this->workLoop = IOWorkLoop::workLoop(); + + if (this->workLoop == nullptr) + { + perr("Failed to create the workloop."); + + goto error; + } + + this->commandGate = IOCommandGate::commandGate(this); + + if (this->commandGate == nullptr) + { + perr("Failed to create the command gate."); + + goto error; + } + + this->workLoop->addEventSource(this->commandGate); + + pinfo("The workloop and the command gate have been created."); + + return true; + +error: + OSSafeReleaseNULL(this->commandGate); + + OSSafeReleaseNULL(this->workLoop); + + pinfo("Failed to create the workloop and the command gate."); + + return false; +} + +/// +/// Probe the index of the message signaled interrupt +/// +/// @return The index on success, `-1` otherwise. +/// +int RealtekPCIeCardReaderController::probeMSIIndex() +{ + int index = -1; + + int interruptType = 0; + + while (this->device->getInterruptType(index, &interruptType) == kIOReturnSuccess) + { + if (interruptType & kIOInterruptTypePCIMessaged) + { + pinfo("Found the MSI index = %d.", index); + + break; + } + + index += 1; + } + + return index; +} + +/// +/// Setup the interrupt management module +/// +/// @return `true` on success, `false` otherwise. +/// +bool RealtekPCIeCardReaderController::setupInterrupts() +{ + // Guard: Probe the MSI interrupt index + int index = this->probeMSIIndex(); + + if (index == -1) + { + pwarning("Failed to probe the MSI index. Will use the default value 0 instead."); + + index = 0; + } + + pinfo("The MSI index = %d. Will set up the interrupt event source.", index); + + // Guard: Setup the interrupt event source + auto filter = OSMemberFunctionCast(IOFilterInterruptEventSource::Filter, this, &RealtekPCIeCardReaderController::interruptEventSourceFilter); + + auto handler = OSMemberFunctionCast(IOFilterInterruptEventSource::Action, this, &RealtekPCIeCardReaderController::interruptHandlerGated); + + this->interruptEventSource = IOFilterInterruptEventSource::filterInterruptEventSource(this, handler, filter, this->device, index); + + if (this->interruptEventSource == nullptr) + { + perr("Failed to create the interrupt event source."); + + return false; + } + + this->workLoop->addEventSource(this->interruptEventSource); + + this->interruptEventSource->enable(); + + pinfo("Interrupt event source and its handler have been registerd."); + + return true; +} + +/// +/// Setup the host command and buffer management module +/// +/// @return `true` on success, `false` otherwise. +/// +bool RealtekPCIeCardReaderController::setupHostBuffer() +{ + // Used to generate the scatter/gather list for the DMA transaction + pinfo("Creating the host command and data buffer..."); + + UInt64 offset = 0; + + IODMACommand::Segment32 segment; + + UInt32 numSegments = 1; + + // Guard: 1. Allocate memory for the host command and data buffer + this->hostBufferDescriptor = IOBufferMemoryDescriptor::withCapacity(4096, kIODirectionInOut, true); + + if (this->hostBufferDescriptor == nullptr) + { + perr("Failed to allocate the memory for the host buffer."); + + goto error1; + } + + // Guard: 2. Setup the DMA command + this->hostBufferDMACommand = IODMACommand::withSpecification(kIODMACommandOutputHost32, 32, 0, IODMACommand::kMapped, 0, 1); + + if (this->hostBufferDMACommand == nullptr) + { + perr("Failed to allocate the DMA command."); + + goto error2; + } + + // Guard: 3. Page in and wire down the buffer + if (this->hostBufferDescriptor->prepare() != kIOReturnSuccess) + { + perr("Failed to prepare the command buffer descriptor."); + + goto error3; + } + + // Guard: 4. Associate the memory descriptor with the DMA command + // Guard: 5. Prepare to start the DMA transaction (auto prepare is set to true) + if (this->hostBufferDMACommand->setMemoryDescriptor(this->hostBufferDescriptor) != kIOReturnSuccess) + { + perr("Failed to associate the memory descriptor with the DMA command and start the transaction."); + + goto error5; + } + + // Guard: 6. Generate the scatter/gather list for the DMA transaction + // The buffer should fit into a single segment + if (this->hostBufferDMACommand->gen32IOVMSegments(&offset, &segment, &numSegments) != kIOReturnSuccess) + { + perr("Failed to generate the segment for the host buffer."); + + goto error6; + } + + // Guard: 7. Setup the timer for the DMA transaction + this->hostBufferTimer = IOTimerEventSource::timerEventSource(this, OSMemberFunctionCast(IOTimerEventSource::Action, this, &RealtekPCIeCardReaderController::bufferTransferTimeoutHandlerGated)); + + if (this->hostBufferTimer == nullptr) + { + perr("Failed to create a timer for the DMA transaction."); + + goto error7; + } + + // Guard: 8. Register the timer event source + if (this->workLoop->addEventSource(this->hostBufferTimer) != kIOReturnSuccess) + { + perr("Failed to register the timer event source to the work loop."); + + goto error8; + } + + // All done: Save the address + this->hostBufferAddress = segment.fIOVMAddr; + + this->hostCommandCount = 0; + + this->hostBufferTransferStatus = kIOReturnSuccess; + + pinfo("The host command and data buffer has been created. Bus address = 0x%08x.", segment.fIOVMAddr); + + return true; + + // Error handlers +error8: + this->hostBufferTimer->release(); + + this->hostBufferTimer = nullptr; + +error7: +error6: + this->hostBufferDMACommand->clearMemoryDescriptor(); + +error5: + this->hostBufferDescriptor->complete(); + +error3: + this->hostBufferDMACommand->release(); + + this->hostBufferDMACommand = nullptr; + +error2: + this->hostBufferDescriptor->release(); + + this->hostBufferDescriptor = nullptr; + +error1: + return false; +} + +/// +/// Setup the SD card reader +/// +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `rtsx_pci_init_chip()` defined in `rtsx_pci.c`. +/// +bool RealtekPCIeCardReaderController::setupCardReader() +{ + // Initialize the device-specific parameters + pinfo("Initializing the device-specific parameters..."); + + if (this->initParameters() != kIOReturnSuccess) + { + perr("Failed to initialize device-specific parameters."); + + return false; + } + + pinfo("Device-specific parameters have been initialized."); + + // Fetch the vendor-specific parameters + pinfo("Initializing the vendor-specific parameters..."); + + if (this->initVendorSpecificParameters() != kIOReturnSuccess) + { + perr("Failed to initialize vendor-specific parameters."); + + return false; + } + + pinfo("Vendor-specific parameters have been initialized."); + + // Initialize the card reader + pinfo("Initializing the card reader..."); + + if (this->initHardwareCommon() != kIOReturnSuccess) + { + perr("Failed to initialize the card reader."); + + return false; + } + + pinfo("The card reader has been initialized."); + + return true; +} + +/// +/// Create the card slot and publish it +/// +/// @return `true` on success, `false` otherwise. +/// +bool RealtekPCIeCardReaderController::createCardSlot() +{ + pinfo("Creating the card slot..."); + + RealtekSDXCSlot* slot = OSTypeAlloc(RealtekSDXCSlot); + + if (slot == nullptr) + { + perr("Failed to allocate the card slot."); + + return false; + } + + if (!slot->init()) + { + perr("Failed to initialize the card slot."); + + slot->release(); + + return false; + } + + if (!slot->attach(this)) + { + perr("Failed to attach the card slot."); + + slot->release(); + + return false; + } + + if (!slot->start(this)) + { + perr("Failed to start the card slot."); + + slot->detach(this); + + slot->release(); + + return false; + } + + this->slot = slot; + + pinfo("The card slot has been created and published."); + + return true; +} + +// +// MARK: - Teardown Routines +// + +/// +/// Tear down the interrupt event source +/// +void RealtekPCIeCardReaderController::tearDownInterrupts() +{ + if (this->interruptEventSource != nullptr) + { + this->interruptEventSource->disable(); + + this->workLoop->removeEventSource(this->interruptEventSource); + + this->interruptEventSource->release(); + + this->interruptEventSource = nullptr; + } +} + +/// +/// Tear down the workloop +/// +void RealtekPCIeCardReaderController::tearDownWorkLoop() +{ + OSSafeReleaseNULL(this->commandGate); + + OSSafeReleaseNULL(this->workLoop); +} + +/// +/// Tear down the host command and buffer management module +/// +void RealtekPCIeCardReaderController::tearDownHostBuffer() +{ + // R7: Cancel the timer for the DMA transaction + // R8: Deregister the timer event source + if (this->hostBufferTimer != nullptr) + { + this->hostBufferTimer->cancelTimeout(); + + psoftassert(this->workLoop->removeEventSource(this->hostBufferTimer) == kIOReturnSuccess, + "Failed to remove the timer event source from the work loop,"); + + this->hostBufferTimer->release(); + + this->hostBufferTimer = nullptr; + } + + // R5: Complete the DMA transaction (auto complete is set to true) + // R4: Deassociate with the buffer descriptor + // R2: Release the DMA command + if (this->hostBufferDMACommand != nullptr) + { + psoftassert(this->hostBufferDMACommand->clearMemoryDescriptor() == kIOReturnSuccess, + "Failed to deassociate the buffer descriptor with the DMA command and stop the transaction."); + + this->hostBufferDMACommand->release(); + + this->hostBufferDMACommand = nullptr; + } + + // R3: Unwire and release the buffer + if (this->hostBufferDescriptor != nullptr) + { + psoftassert(this->hostBufferDescriptor->complete() == kIOReturnSuccess, + "Failed to complete the command buffer descriptor."); + + this->hostBufferDescriptor->release(); + + this->hostBufferDescriptor = nullptr; + } +} + +/// +/// Unmap the device memory +/// +void RealtekPCIeCardReaderController::unmapDeviceMemory() +{ + this->deviceMemoryDescriptor->complete(); + + this->deviceMemoryDescriptor = nullptr; + + OSSafeReleaseNULL(this->deviceMemoryMap); +} + +/// +/// Destroy the card slot +/// +void RealtekPCIeCardReaderController::destroyCardSlot() +{ + pinfo("Stopping the card slot..."); + + if (this->slot != nullptr) + { + this->slot->stop(this); + + this->slot->detach(this); + + OSSafeReleaseNULL(this->slot); + } +} + +// +// MARK: - IOService Implementations +// + +/// +/// Start the controller +/// +/// @param provider An instance of PCI device that represents the card reader +/// @return `true` on success, `false` otherwise. +/// +bool RealtekPCIeCardReaderController::start(IOService* provider) +{ + pinfo("==================================================="); + pinfo("Starting the Realtek PCIe card reader controller..."); + pinfo("==================================================="); + + // Start the super class + if (!super::start(provider)) + { + perr("Failed to start the super class."); + + return false; + } + + // Set up the controller device + this->device = OSDynamicCast(IOPCIDevice, provider); + + if (this->device == nullptr) + { + perr("The provider is not an valid PCI device."); + + return false; + } + + this->device->retain(); + + this->device->setBusMasterEnable(true); + + this->device->setMemoryEnable(true); + + // Set up the memory map + if (!this->mapDeviceMemory()) + { + perr("Failed to map the device memory."); + + goto error1; + } + + // Set up the work loop + if (!this->setupWorkLoop()) + { + perr("Failed to set up the work loop."); + + goto error2; + } + + // Set up the hardware interrupt + if (!this->setupInterrupts()) + { + perr("Failed to set up the hardware interrupt."); + + goto error3; + } + + // Set up the host buffer + if (!this->setupHostBuffer()) + { + perr("Failed to set up the host buffer."); + + goto error4; + } + + // Fetch the current ASPM state + this->parameters.pm.isASPMEnabled = this->isASPMEnabled(); + + pinfo("ASPM Enabled: %s.", YESNO(this->parameters.pm.isASPMEnabled)); + + // Set up the card reader + if (!this->setupCardReader()) + { + perr("Failed to set up the card reader."); + + goto error5; + } + + // Create the card slot + if (!this->createCardSlot()) + { + perr("Failed to create the card slot."); + + goto error5; + } + + // If the card is already inserted when the driver starts, + // there will be no card interrupt, so we check whether the card exists here. + if (this->isCardPresent()) + { + pinfo("Detected a card when the driver starts. Will notify the host device."); + + // Notify the host device + this->onSDCardInsertedGated(); + } + else + { + pinfo("The card is not present when the driver starts."); + } + + this->registerService(); + + this->isIdle = true; + + pinfo("ASPM Enabled: %s.", YESNO(this->isASPMEnabled())); + + pinfo("================================================"); + pinfo("The card reader controller started successfully."); + pinfo("================================================"); + + return true; + +error5: + this->tearDownHostBuffer(); + +error4: + this->tearDownInterrupts(); + +error3: + this->tearDownWorkLoop(); + +error2: + this->unmapDeviceMemory(); + +error1: + this->device->setMemoryEnable(false); + + this->device->setBusMasterEnable(false); + + OSSafeReleaseNULL(this->device); + + pinfo("==========================================="); + pinfo("Failed to start the card reader controller."); + pinfo("==========================================="); + + return false; +} + +/// +/// Stop the controller +/// +/// @param provider An instance of PCI device that represents the card reader +/// +void RealtekPCIeCardReaderController::stop(IOService* provider) +{ + this->destroyCardSlot(); + + this->tearDownHostBuffer(); + + this->tearDownInterrupts(); + + this->tearDownWorkLoop(); + + this->unmapDeviceMemory(); + + this->device->setMemoryEnable(false); + + this->device->setBusMasterEnable(false); + + OSSafeReleaseNULL(this->device); + + super::stop(provider); +} diff --git a/RealtekPCIeCardReader/RealtekPCIeCardReaderController.hpp b/RealtekPCIeCardReader/RealtekPCIeCardReaderController.hpp new file mode 100644 index 0000000..b28a331 --- /dev/null +++ b/RealtekPCIeCardReader/RealtekPCIeCardReaderController.hpp @@ -0,0 +1,1613 @@ +// +// RealtekPCIeCardReaderController.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/18/21. +// + +#ifndef RealtekPCIeCardReaderController_hpp +#define RealtekPCIeCardReaderController_hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Utilities.hpp" +#include "Registers.hpp" +#include "Debug.hpp" +#include "AppleSDXC.hpp" +#include "BitOptions.hpp" +#include "ClosedRange.hpp" + +// TODO: RELOCATED THIS +/// TX/RX clock phase +struct ClockPhase +{ + UInt8 sdr104, sdr50, ddr50; + + ClockPhase() + : sdr104(0), sdr50(0), ddr50(0) {} + + ClockPhase(UInt8 sdr104, UInt8 sdr50, UInt8 ddr50) + : sdr104(sdr104), sdr50(sdr50), ddr50(ddr50) {} +}; + +/// The card output volage +enum class OutputVoltage: UInt32 +{ + k3d3V, k1d8V +}; + +/// Enumerates all possible SSC depth values +enum class SSCDepth: UInt32 +{ + k4M = 0x01, + k2M = 0x02, + k1M = 0x03, + k500K = 0x04, + k250K = 0x05 +}; + +#define LTR_ACTIVE_LATENCY_DEF 0x883C +#define LTR_IDLE_LATENCY_DEF 0x892C +#define LTR_L1OFF_LATENCY_DEF 0x9003 +#define L1_SNOOZE_DELAY_DEF 1 +#define LTR_L1OFF_SSPWRGATE_5249_DEF 0xAF +#define LTR_L1OFF_SSPWRGATE_5250_DEF 0xFF +#define LTR_L1OFF_SNOOZE_SSPWRGATE_5249_DEF 0xAC +#define LTR_L1OFF_SNOOZE_SSPWRGATE_5250_DEF 0xF8 + +/// Forward Declaration (Client of the card reader controller) +class RealtekSDXCSlot; + +/// +/// Represents the abstract PCIe card reader controller +/// +/// @note This is the base class of all device-specific controllers. +/// +class RealtekPCIeCardReaderController: public AppleSDXC +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareAbstractStructors(RealtekPCIeCardReaderController); + + using super = AppleSDXC; + + // + // MARK: - Constants + // + + /// The minimum SSC clock divider value + static constexpr UInt32 kMinSSCClockDividerN = 80; + + /// The maximum SSC clock divider value + static constexpr UInt32 kMaxSSCClockDividerN = 208; + + /// The amount of time in microseconds to wait until the SSC clock becomes stable + static constexpr UInt32 kWaitStableSSCClock = 130; + + // + // MARK: - Data Structures + // + + /// Represents a command in the host command buffer + struct Command + { + /// All supported command types + enum Type + { + kReadRegister = 0, + kWriteRegister = 1, + kCheckRegister = 2, + }; + + /// The 32-bit value that will be written to the command buffer + UInt32 value; + + /// + /// Create a command of the given type and arguments + /// + /// @param type The command type + /// @param address The register address + /// @param mask The register value mask (ignored if `type` is `kReadRegister`) + /// @param value The register value (ignored if `type` is `kReadRegister`) + /// + Command(Type type, UInt16 address, UInt8 mask, UInt8 value) + { + this->value = 0; + + this->value |= static_cast(type & 0x03) << 30; + + this->value |= static_cast(address & 0x3FFF) << 16; + + this->value |= static_cast(mask) << 8; + + this->value |= static_cast(value); + } + }; + + /// An entry in the driving table + struct DrivingEntry + { + const UInt8 clock, command, data; + + DrivingEntry(UInt8 clock, UInt8 command, UInt8 data) + : clock(clock), command(command), data(data) {} + }; + + /// The driving table + struct DrivingTable + { + /// Type D, C, A, B + const DrivingEntry entries[4]; + }; + + /// IC Revision + enum class Revision: UInt8 + { + kA = 0, kB = 1, kC = 2, kD = 3, kUnknown = 0xFF + }; + + /// Device-specific parameters + struct Parameters + { + /// IC revision + Revision revision; + + /// The number of slots + UInt8 numSlots; + + /// Host capabilities + struct Capabilities + { + /// Supports SD UHS SDR50 mode + bool supportsSDSDR50: 1; + + /// Supports SD UHS SDR104 mode + bool supportsSDSDR104: 1; + + /// Supports SD UHS DDR50 mode + bool supportsSDDDR50: 1; + + /// Supports MMC DDR 1.8V + bool supportsMMCDDR1d8V: 1; + + /// Supports 8 bit transfers + bool supportsMMC8BitTransfer: 1; + + /// Do not send MMC commands during initialization + bool noMMCCommandsDuringInitialization: 1; + + /// Supports SD Express + bool supportsSDExpress: 1; + + /// Reserved Bits + bool reserved: 1; + + } caps; + + /// True if the socket is reversed + bool isSocketReversed; + + /// Card drive selector + UInt8 cardDriveSelector; + + /// SD 3.0 drive selector (1.8V) + UInt8 sd30DriveSelector1d8V; + + /// SD 3.0 drive selector (3.3V) + UInt8 sd30DriveSelector3d3V; + + /// SD 3.0 drive table (1.8V) (Nullable) + const DrivingTable* sd30DriveTable1d8V; + + /// SD 3.0 drive table (3.3V) (Nullable) + const DrivingTable* sd30DriveTable3d3V; + + /// A non-null sequence of chip registers to enable SD pull control + const SimpleRegValuePairs* sdEnablePullControlTable; + + /// A non-null sequence of chip registers to disable SD pull control + const SimpleRegValuePairs* sdDisablePullControlTable; + + /// Initial TX clock phase + ClockPhase initialTxClockPhase; + + /// Initial RX clock phase + ClockPhase initialRxClockPhase; + + /// Overcurrent Protection Parameters + struct OCP + { + /// Set to `true` if overcurrent protection should be enabled + bool enable; + + /// Total harmonic distortion value (400mA) + UInt8 sdTHD400mA; + + /// Total harmonic distortion value (800mA) + UInt8 sdTHD800mA; + + /// Glitch value + UInt8 sdGlitch; + } ocp; + + /// Power Management + struct PowerManagement + { + /// `True` if ASPM L1 is enabled + bool isASPML1Enabled; + + /// `True` if ASPM is enabled + bool isASPMEnabled; + + /// Enable PCI-PM L1.1 + bool enablePCIPML11; + + /// Enable PCI-PM L1.2 + bool enablePCIPML12; + + /// Enable ASPM L1.1 + bool enableASPML11; + + /// Enable ASPM L1.2 + bool enableASPML12; + + /// Enable LTR L1SS power gate + bool enableLTRL1SSPowerGate; + + /// Enable LTR L1SS power gate and card check + bool enableLTRL1SSPowerGateCheckCard; + + /// Enable L1 snooze test + bool enableL1SnoozeTest; + + /// Force clock request + bool forceClockRequest; + + /// Enable LTR mode + bool enableLTRMode; + + /// Is LTR mode enabled + bool isLTRModeEnabled; + + /// Is LTR mode active + bool isLTRModeActive; + + /// LTR mode active latency + UInt32 ltrActiveLatency; + + /// LTR mode idle latency + UInt32 ltrIdleLatency; + + /// LTR mode L1 off latency + UInt32 ltrL1OffLatency; + + /// LTR snooze delay + UInt32 ltrSnoozeDelay; + + /// LTR L1 off substate power gate + UInt8 ltrL1OffSSPowerGate; + + /// LTR L1 off snooze substate power gate + UInt8 ltrL1OffSnoozeSSPowerGate; + } pm; + }; + + // + // MARK: - IOKit Basics + // + + /// The PCIe card reader device (provider) + IOPCIDevice* device; + + /// The SD card slot (client) + RealtekSDXCSlot* slot; + + /// A mapping for the device memory + IOMemoryMap* deviceMemoryMap; + + /// Device memory at BARx where x may vary device + IOMemoryDescriptor* deviceMemoryDescriptor; + + /// Protect shared resources and serialize operations + IOWorkLoop* workLoop; + + /// + /// A command gate to serialize executions with respect to the work loop + /// + /// @note This command gate is not associated with any specific actions. + /// The caller should use `runAction` to execute a function in a gated context. + /// @note The target action is still executed in the context of the calling thread. + /// + IOCommandGate* commandGate; + + /// An event source that delivers the hardware interrupt + IOFilterInterruptEventSource* interruptEventSource; + + // MARK: - Host Command & Data Buffer + + /// The host buffer size (by default 4096 bytes) + static constexpr IOByteCount kHostBufferSize = RTSX::MMIO::HCBAR::kMaxNumCmds * 4 + RTSX::MMIO::HDBAR::kMaxNumElements * 8; + + /// The host command buffer starts at the offset 0 in the host buffer + static constexpr IOByteCount kHostCommandBufferOffset = 0; + + /// The host data buffer starts at the offset 1024 in the host buffer + static constexpr IOByteCount kHostDatabufferOffset = RTSX::MMIO::HCBAR::kMaxNumCmds * 4; + + /// + /// A descriptor that allocates the host command and data buffer + /// + /// @note The buffer size is 4 KB, in which + /// 1 KB is reserved for the host command buffer, allowing 256 commands to be queued; + /// 3 KB is reserved for the host data buffer, allowing 384 scatter/gather list items to be queued. + /// @note The buffer remains "prepared" until the driver is stopped by the system. + /// The lifecycle is managed by `setupHostBuffer()` and `tearDownHostBuffer()`. + /// @note The buffer direction is set to be `kIODirectionInOut`. + /// + IOBufferMemoryDescriptor* hostBufferDescriptor; + + /// + /// Translate the host buffer address to I/O bus address + /// + /// @note The command remains "prepared" until the driver is stopped by the system. + /// The lifecycle is managed by `setupHostBuffer()` and `tearDownHostBuffer()`. + /// @note The command remains associated with the host buffer descriptor, + /// and data is always synchronized with that descriptor. + /// @note Accesses to the host buffer must be done via `IODMACommand::write/readBytes()`. + /// + IODMACommand* hostBufferDMACommand; + + /// A timer that runs the timeout handler for the current buffer transfer session + IOTimerEventSource* hostBufferTimer; + + /// + /// The base address of the host buffer that can be used by the DMA engine + /// + /// @note The command buffer starts at offset 0, while the data buffer starts at offset 1024. + /// + IOPhysicalAddress32 hostBufferAddress; + + /// The number of commands in the host command buffer + IOItemCount hostCommandCount; + + /// + /// Status of the current buffer transfer session + /// + /// @note The status is reset by the buffer transfer routines and modified by the timeout handler or the interrupt handler. + /// `kIOReturnNotReady` if the transfer has not yet started; + /// `kIOReturnSuccess` if the transfer has succeeded; + /// `kIOReturnError` if the transfer has failed; + /// `kIOReturnTimeout` if the transfer has timed out. + /// + IOReturn hostBufferTransferStatus; + + // + // MARK: - Device Specific Properties + // + + /// Device-specific paramters + Parameters parameters; + + // + // MARK: - Host States + // + + /// The current SSC clock in MHz + UInt32 currentSSCClock; + + /// `True` if the host is idle + bool isIdle; + + // + // MARK: - Query Controller Capabilities + // + +public: + /// Check whether the card reader supports the UHS-I SDR50 mode + inline bool supportsSDR50() + { + return this->parameters.caps.supportsSDSDR50; + } + + /// Check whether the card reader supports the UHS-I SDR104 mode + inline bool supportsSDR104() + { + return this->parameters.caps.supportsSDSDR104; + } + + /// Check whether the card reader supports the UHS-I DDR50 mode + inline bool supportsDDR50() + { + return this->parameters.caps.supportsSDDDR50; + } + + // + // MARK: - Access Memory Mapped Registers (Common, Final) + // + +private: + /// + /// Read from a memory mapped register at the given address + /// + /// @param address The register address + /// @return The register value. + /// + template + T readRegister(UInt32 address) + { + T value; + + psoftassert(this->deviceMemoryDescriptor->readBytes(address, &value, sizeof(T)) == sizeof(T), + "Failed to read %lu bytes from the register at 0x%x.", sizeof(T), address); + + return value; + } + + /// + /// Write to a memory mapped register at the given address + /// + /// @param address The register address + /// @param value The register value + /// + template + void writeRegister(UInt32 address, T value) + { + psoftassert(this->deviceMemoryDescriptor->writeBytes(address, &value, sizeof(T)) == sizeof(T), + "Failed to write %lu bytes to the register at 0x%x.", sizeof(T), address); + } + +public: + /// + /// Read a byte from a memory mapped register at the given address + /// + /// @param address The register address + /// @return The register value. + /// @note Port: This function replaces the macro `rtsx_pci_readb()` defined in `rtsx_pci.h`. + /// + UInt8 readRegister8(UInt32 address); + + /// + /// Read a word from a memory mapped register at the given address + /// + /// @param address The register address + /// @return The register value. + /// @note Port: This function replaces the macro `rtsx_pci_readw()` defined in `rtsx_pci.h`. + /// + UInt16 readRegister16(UInt32 address); + + /// + /// Read a double word from a memory mapped register at the given address + /// + /// @param address The register address + /// @return The register value. + /// @note Port: This function replaces the macro `rtsx_pci_readl()` defined in `rtsx_pci.h`. + /// + UInt32 readRegister32(UInt32 address); + + /// + /// Write a byte to a memory mapped register at the given address + /// + /// @param address The register address + /// @param value The register value + /// @note Port: This function replaces the macro `rtsx_pci_writeb()` defined in `rtsx_pci.h`. + /// + void writeRegister8(UInt32 address, UInt8 value); + + /// + /// Write a word to a memory mapped register at the given address + /// + /// @param address The register address + /// @param value The register value + /// @note Port: This function replaces the macro `rtsx_pci_writew()` defined in `rtsx_pci.h`. + /// + void writeRegister16(UInt32 address, UInt16 value); + + /// + /// Write a double word to a memory mapped register at the given address + /// + /// @param address The register address + /// @param value The register value + /// @note Port: This function replaces the macro `rtsx_pci_writel()` defined in `rtsx_pci.h`. + /// + void writeRegister32(UInt32 address, UInt32 value); + + // + // MARK: - Access Chip Registers (Common, Final) + // + + /// + /// Read a byte from the chip register at the given address + /// + /// @param address The register address + /// @param value The register value on return + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out. + /// @note Port: This function replaces `rtsx_pci_read_register()` defined in `rtsx_psr.c`. + /// + IOReturn readChipRegister(UInt16 address, UInt8& value); + + /// + /// Write a byte to the chip register at the given address + /// + /// @param address The register address + /// @param mask The register value mask + /// @param value The register value + /// @return `kIOReturnSuccess` on success; + /// `kIOReturnTimeout` if timed out; + /// `kIOReturnError` if the new register value is not `value`. + /// @note Port: This function replaces `rtsx_pci_write_register()` defined in `rtsx_psr.c`. + /// + IOReturn writeChipRegister(UInt16 address, UInt8 mask, UInt8 value); + + /// + /// Write to multiple chip registers conveniently + /// + /// @param pairs A sequence of pairs of register address and value + /// @return `kIOReturnSuccess` on success; + /// `kIOReturnTimeout` if timed out; + /// `kIOReturnError` if the new register value is not `value`. + /// + IOReturn writeChipRegisters(const ChipRegValuePairs& pairs); + + /// + /// Dump the chip registers in the given range + /// + /// @param range The range of register addresses + /// + void dumpChipRegisters(ClosedRange range); + + // + // MARK: - Access Physical Layer Registers (Default, Overridable) + // + + /// + /// Read a word from the physical layer register at the given address + /// + /// @param address The register address + /// @param value The register value on return + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_read_phy_register()` defined in `rtsx_psr.c`. + /// @note Subclasses may override this function to provide a device-specific implementation. + /// + virtual IOReturn readPhysRegister(UInt8 address, UInt16& value); + + /// + /// Write a word to the physical layer register at the given address + /// + /// @param address The register address + /// @param value The register value + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_write_phy_register()` defined in `rtsx_psr.c`. + /// @note Subclasses may override this function to provide a device-specific implementation. + /// + virtual IOReturn writePhysRegister(UInt8 address, UInt16 value); + + /// + /// Write to multiple physical layer registers conveniently + /// + /// @param pairs An array of registers and their values + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// + IOReturn writePhysRegisters(const PhysRegValuePair* pairs, size_t count); + + /// + /// Write to multiple physical layer registers conveniently + /// + /// @param pairs An array of registers and their values + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// + template + IOReturn writePhysRegisters(const PhysRegValuePair (&pairs)[N]) + { + return this->writePhysRegisters(pairs, N); + } + + /// The type of a function that modifies the given physical register value + using PhysRegValueModifier = UInt16 (*)(UInt16, const void*); + + /// + /// Modify a physical layer register conveniently + /// + /// @param address The register address + /// @param modifier A modifier that takes the current register value and return the new value + /// @param context A nullable context that will be passed to the modifier function + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_update_phy()` defined in `rtsx_pci.h`. + /// + IOReturn modifyPhysRegister(UInt8 address, PhysRegValueModifier modifier, const void* context = nullptr); + + /// + /// Set a bit at the given index in a physical layer register conveniently + /// + /// @param address The register address + /// @param index The zero-based bit index + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// + IOReturn setPhysRegisterBitAtIndex(UInt8 address, UInt32 index); + + /// + /// Clear a bit at the given index in a physical layer register conveniently + /// + /// @param address The register address + /// @param index The zero-based bit index + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// + IOReturn clearPhysRegisterBitAtIndex(UInt8 address, UInt32 index); + + /// + /// Append the given value to a physical layer register conveniently + /// + /// @param address The register address + /// @param mask The register value mask + /// @param append The value to be appended to the current value + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_update_phy()` defined in `rtsx_pci.h`. + /// + IOReturn appendPhysRegister(UInt8 address, UInt16 mask, UInt16 append); + + /// + /// Append each given value to the corresponding physical layer register conveniently + /// + /// @param pairs An array of registers and their masks and values + /// @param count The number of elements in the array + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// + IOReturn appendPhysRegisters(const PhysRegMaskValuePair* pairs, size_t count); + + /// + /// Append each given value to the corresponding physical layer register conveniently + /// + /// @param pairs An array of registers and their masks and values + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// + template + IOReturn appendPhysRegisters(const PhysRegMaskValuePair (&pairs)[N]) + { + return this->appendPhysRegisters(pairs, N); + } + + // + // MARK: - Host Buffer Management + // + + /// + /// Read from the host buffer into the given buffer + /// + /// @param offset A byte offset into the host buffer + /// @param buffer A non-null buffer + /// @param length The number of bytes to read + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function coordinates all accesses to the host buffer. + /// + IOReturn readHostBuffer(IOByteCount offset, void* buffer, IOByteCount length); + + /// + /// Write to the host buffer form the given buffer + /// + /// @param offset A byte offset into the host buffer + /// @param buffer A non-null buffer + /// @param length The number of bytes to write + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function coordinates all accesses to the host buffer. + /// + IOReturn writeHostBuffer(IOByteCount offset, const void* buffer, IOByteCount length); + + /// + /// Write to the host data buffer form the given buffer conveniently + /// + /// @param offset A byte offset into the host data buffer + /// @param buffer A non-null buffer + /// @param length The number of bytes to write + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function coordinates all accesses to the host data buffer. + /// @warning The first argument `offset` is relative to the start address of the host data buffer. + /// i.e. If `offset` is 5, this function will start to read from the offset `1024 + 5` in the host buffer. + /// + inline IOReturn writeHostDataBuffer(IOByteCount offset, const void* buffer, IOByteCount length) + { + return this->writeHostBuffer(RealtekPCIeCardReaderController::kHostDatabufferOffset + offset, buffer, length); + } + + /// + /// Peek a numeric value in the host buffer conveniently + /// + /// @tparam T Specify a numeric type, e.g., S/UInt{8, 16, 32, 64} + /// @param offset A byte offset into the host command buffer + /// @return The numeric value. + /// @note This function will trigger a kernel panic if the given offset is invalid. + /// + template + T peekHostBuffer(IOByteCount offset) + { + T value; + + passert(this->readHostBuffer(offset, &value, sizeof(T)) == kIOReturnSuccess, + "Failed to peek %lu bytes at offset %llu.", sizeof(T), offset); + + return value; + } + + /// + /// Dump the host buffer contents + /// + /// @param offset A byte offset into the host data buffer + /// @param length The number of bytes to dump + /// @param column The number of columns to print + /// + void dumpHostBuffer(IOByteCount offset, IOByteCount length, IOByteCount column = 8); + + // + // MARK: - Host Command Management + // + + /// + /// Start a new host command transfer session + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note The caller must invoke this function before any subsequent calls to `enqueue*Command()`. + /// Any commands enqueued before this function call will be overwritten. + /// Once all commands are enqueued, the caller should invoke `endCommandTransfer()` to send commands to the device. + /// @note Port: This function replaces `rtsx_pci_init_cmd()` defined in `rtsx_pci.h`. + /// + IOReturn beginCommandTransfer(); + + /// + /// Enqueue a command + /// + /// @param command The command + /// @return `kIOReturnSuccess` on success, `kIOReturnBusy` if the command buffer is full, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_add_cmd()` defined in `rtsx_psr.c`. + /// + IOReturn enqueueCommand(const Command& command); + + /// + /// Enqueue a command to read the register at the given address conveniently + /// + /// @param address The register address + /// @return `kIOReturnSuccess` on success, `kIOReturnBusy` if the command buffer is full. + /// @note Port: This function replaces `rtsx_pci_add_cmd()` defined in `rtsx_psr.c`. + /// + IOReturn enqueueReadRegisterCommand(UInt16 address); + + /// + /// Enqueue a command to write a value to the register at the given address conveniently + /// + /// @param address The register address + /// @param mask The register value mask + /// @param value The register value + /// @return `kIOReturnSuccess` on success, `kIOReturnBusy` if the command buffer is full. + /// @note Port: This function replaces `rtsx_pci_add_cmd()` defined in `rtsx_psr.c`. + /// + IOReturn enqueueWriteRegisterCommand(UInt16 address, UInt8 mask, UInt8 value); + + /// + /// Enqueue a command to check the value of the register at the given address conveniently + /// + /// @param address The register address + /// @param mask The register value mask + /// @param value The register value + /// @return `kIOReturnSuccess` on success, `kIOReturnBusy` if the command buffer is full. + /// @note Port: This function replaces `rtsx_pci_add_cmd()` defined in `rtsx_psr.c`. + /// + IOReturn enqueueCheckRegisterCommand(UInt16 address, UInt8 mask, UInt8 value); + + /// + /// Finish the existing host command transfer session + /// + /// @param timeout Specify the amount of time in milliseconds + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_send_cmd()` defined in `rtsx_psr.c`. + /// @note This function sends all commands in the queue to the device. + /// + IOReturn endCommandTransfer(UInt32 timeout = 100); + + /// + /// Finish the existing host command transfer session without waiting for the completion interrupt + /// + /// @note Port: This function replaces `rtsx_pci_send_cmd_no_wait()` defined in `rtsx_psr.c`. + /// @note This function sends all commands in the queue to the device. + /// + void endCommandTransferNoWait(); + + /// + /// Enqueue a sequence of commands to read registers conveniently + /// + /// @param pairs A sequence of pairs of register address and value + /// @return `kIOReturnSuccess` on success, `kIOReturnError` otherwise. + /// @note This function provides a elegant way to enqueue multiple commands and handle errors. + /// + IOReturn enqueueReadRegisterCommands(const ChipRegValuePairs& pairs); + + /// + /// Enqueue a sequence of commands to write registers conveniently + /// + /// @param pairs A sequence of pairs of register address and value + /// @return `kIOReturnSuccess` on success, `kIOReturnError` otherwise. + /// @note This function provides a elegant way to enqueue multiple commands and handle errors. + /// + IOReturn enqueueWriteRegisterCommands(const ChipRegValuePairs& pairs); + + /// + /// Transfer a sequence of commands to read registers conveniently + /// + /// @param pairs A sequence of pairs of register address and value + /// @param timeout Specify the amount of time in milliseconds + /// @return `kIOReturnSuccess` on success, `kIOReturnError` otherwise. + /// @note This function provides a elegant way to start a command transfer session and handle errors. + /// Same as calling `startCommandTransfer`, a sequence of `enqueueReadRegisterCommand` and `endCommandTransfer`. + /// + IOReturn transferReadRegisterCommands(const ChipRegValuePairs& pairs, UInt32 timeout = 100); + + /// + /// Transfer a sequence of commands to write registers conveniently + /// + /// @param pairs A sequence of pairs of register address and value + /// @param timeout Specify the amount of time in milliseconds + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note This function provides a elegant way to start a command transfer session and handle errors. + /// Same as calling `startCommandTransfer`, a sequence of `enqueueWriteRegisterCommand` and `endCommandTransfer`. + /// + IOReturn transferWriteRegisterCommands(const ChipRegValuePairs& pairs, UInt32 timeout = 100); + + /// + /// Tell the device to stop transferring commands + /// + /// @note Port: This function replaces `rtsx_pci_stop_cmd()` defined in `rtsx_psr.c`. + /// @note Subclasses may override this function to provide device-specific implementation. + /// + virtual IOReturn stopTransfer(); + + // + // MARK: - Host Data Management + // + + /// + /// [Helper] Convert a physical scatter/gather entry to a value to be stored in the host data buffer + /// + /// @param segment An entry in the scatter/gather list + /// @return The value to be written to the host data buffer. + /// @note Port: This helper function is refactored from `rtsx_pci_add_sg_tbl()` defined in `rtsx_psr.c`. + /// RTS5261 and RTS5208 controllers must override this function but should not include the `option` part in the returned value. + /// + virtual UInt64 transformIOVMSegment(IODMACommand::Segment32 segment); + + /// + /// [Helper] Generate a physical scather/gather list from the given DMA command and enqueue all entries into the host data buffer + /// + /// @param command A non-null, perpared DMA command + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This helper function replaces `rtsx_pci_add_sg_tbl()` defined in `rtsx_psr.c`. + /// @warning The caller must ensure that the given instance of `IODMACommand` is prepared. + /// + IOReturn enqueueDMACommand(IODMACommand* command); + + /// + /// [Helper] Perform a DMA transfer + /// + /// @param command A non-null, perpared DMA command + /// @param timeout Specify the amount of time in milliseconds + /// @param control Specify the value that will be written to the register `HDBCTLR` to customize the DMA transfer + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_dma_transfer()` defined in `rtsx_psr.c`. + /// @note This helper function is invoked by both `performDMARead()` and `performDMAWrite()`. + /// The caller should avoid calling this function directly. + /// @warning The caller must ensure that the given instance of `IODMACommand` is prepared. + /// + IOReturn performDMATransfer(IODMACommand* command, UInt32 timeout, UInt32 control); + + /// + /// Perform a DMA read operation + /// + /// @param command A non-null, perpared DMA command + /// @param timeout Specify the amount of time in milliseconds + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// + IOReturn performDMARead(IODMACommand* command, UInt32 timeout); + + /// + /// Perform a DMA write operation + /// + /// @param command A non-null, perpared DMA command + /// @param timeout Specify the amount of time in milliseconds + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// + IOReturn performDMAWrite(IODMACommand* command, UInt32 timeout); + + // + // MARK: - LED Management + // + + /// + /// Turn on the LED + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `turn_on_led()` defined in `struct pcr_ops`. + /// + virtual IOReturn turnOnLED() = 0; + + /// + /// Turn off the LED + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `turn_off_led()` defined in `struct pcr_ops`. + /// + virtual IOReturn turnOffLED() = 0; + + /// + /// Enable LED blinking + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `enable_auto_blink()` defined in `struct pcr_ops`. + /// + virtual IOReturn enableLEDBlinking() = 0; + + /// + /// Disable LED blinking + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `enable_auto_blink()` defined in `struct pcr_ops`. + /// + virtual IOReturn disableLEDBlinking() = 0; + + // + // MARK: - Card Power Management + // + + /// + /// Power on the SD card + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_card_power_on()` defined in `rtsx_psr.c`. + /// + virtual IOReturn powerOnCard() = 0; + + /// + /// Power off the SD card + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_card_power_off()` defined in `rtsx_psr.c`. + /// + virtual IOReturn powerOffCard() = 0; + + /// + /// Switch to the given output voltage + /// + /// @param outputVoltage The new output voltage + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_switch_output_voltage()` defined in `rtsx_psr.c`. + /// + virtual IOReturn switchOutputVoltage(OutputVoltage outputVoltage) = 0; + + /// + /// Set the driving parameters for the given output voltage + /// + /// @param outputVoltage The target output voltage + /// @param intermediate Pass `false` to begin and complete a new session to set up the driving; + /// Pass `true` to enqueue commands that are needed to set up the driving only. + /// @param timeout Specify the amount of time in milliseconds if `intermediate` is set to `false`. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `*_fill_driving()` defined in each concrete controller file. + /// + virtual IOReturn setDrivingForOutputVoltage(OutputVoltage outputVoltage, bool intermediate, UInt32 timeout); + + // + // MARK: - Clock Configurations + // + + /// + /// Adjust the card clock if DMA transfer errors occurred + /// + /// @param cardClock The current card clock + /// @return The adjusted card clock. + /// @note Port: This function replaces the code block that reduces the card clock in `rtsx_pci_switch_clock()` defined in `rtsx_psr.c`. + /// By default, this function does not adjust the card clock and return the given clock. + /// RTS5227 controller must override this function. + /// + virtual UInt32 getAdjustedCardClockOnDMAError(UInt32 cardClock); + + /// + /// Convert the given SSC clock to the divider N + /// + /// @param clock The SSC clock in MHz + /// @return The divider N. + /// @note Port: This function replaces `conv_clk_and_div_n()` defined in `rtsx_psr.c`. + /// RTL8411 series controllers must override this function. + /// + virtual UInt32 sscClock2DividerN(UInt32 clock); + + /// + /// Convert the given divider N back to the SSC clock + /// + /// @param n The divider N + /// @return The SSC clock in MHz. + /// @note Port: This function replaces `conv_clk_and_div_n()` defined in `rtsx_psr.c`. + /// RTL8411 series controllers must override this function. + /// + virtual UInt32 dividerN2SSCClock(UInt32 n); + + /// + /// Switch to the given card clock + /// + /// @param cardClock The card clock in Hz + /// @param sscDepth The SSC depth value + /// @param initialMode Pass `true` if the card is at its initial stage + /// @param doubleClock Pass `true` if the SSC clock should be doubled + /// @param vpclock Pass `true` if VPCLOCK is used + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_switch_clock()` defined in `rtsx_psr.c`. + /// RTS5261 and RTS5228 controllers must override this function. + /// + virtual IOReturn switchCardClock(UInt32 cardClock, SSCDepth sscDepth, bool initialMode, bool doubleClock, bool vpclock); + + // + // MARK: - Card Detection and Write Protection + // + + /// + /// Check whether the card has write protection enabled + /// + /// @return `true` if the card is write protected, `false` otherwise. + /// + bool isCardWriteProtected(); + + /// + /// Check whether a card is present + /// + /// @return `true` if a card exists, `false` otherwise. + /// @note Port: This function replaces `rtsx_pci_card_exist()` and `cd_deglitch()` defined in `rtsx_psr.c`. + /// @warning: This function supports SD cards only. + /// + virtual bool isCardPresent(); + + // + // MARK: - Overcurrent Protection Support + // + + /// + /// Initialize and enable overcurrent protection + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_init_ocp()` defined in `rtsx_psr.c`. + /// + virtual IOReturn initOvercurrentProtection(); + + /// + /// Enable overcurrent protection + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_enable_ocp()` defined in `rtsx_psr.c`. + /// + virtual IOReturn enableOvercurrentProtection(); + + /// + /// Disable overcurrent protection + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_disable_ocp()` defined in `rtsx_psr.c`. + /// + virtual IOReturn disableOvercurrentProtection(); + + /// + /// Get the overcurrent protection status + /// + /// @param status The status on return + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_get_ocpstat()` defined in `rtsx_psr.c`. + /// + virtual IOReturn getOvercurrentProtectionStatus(UInt8& status); + + /// + /// Clear the overcurrent protection status + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_clear_ocpstat()` defined in `rtsx_psr.c`. + /// + virtual IOReturn clearOvercurrentProtectionStatus(); + + // + // MARK: - Card Pull Control Management + // + + /// + /// Enable pull control for the SD card + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_card_pull_ctl_enable()` defined in `rtsx_psr.c`. + /// + IOReturn enablePullControlForSDCard(); + + /// + /// Disable pull control for the SD card + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_card_pull_ctl_disable()` defined in `rtsx_psr.c`. + /// + IOReturn disablePullControlForSDCard(); + + // + // MARK: - OOBS Polling + // + + /// + /// Check whether the driver needs to modify the PHY::RCR0 register to enable or disable OOBS polling + /// + /// @return `true` if the controller is not RTS525A nor RTS5260. + /// @note RTS525A and RTS5260 controllers should override this function and return `false`. + /// + virtual bool oobsPollingRequiresPhyRCR0Access(); + + /// + /// Check whether the hardware supports OOBS polling + /// + /// @return `true` if supported, `false` otherwise. + /// @note By default, this function returns `false`. + /// @note e.g., RTS522A, RTS524A and RTS525A should override this function and return `true`. + /// + virtual bool supportsOOBSPolling(); + + /// + /// Enable OOBS polling + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn enableOOBSPolling(); + + /// + /// Disable OOBS polling + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn disableOOBSPolling(); + + // + // MARK: - Ping Pong Buffer + // + + /// + /// Read from the ping pong buffer + /// + /// @param destination The buffer to store bytes + /// @param length The number of bytes to read (cannot exceed 512 bytes) + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_read_ppbuf()` defined in `rtsx_psr.c`. + /// + IOReturn readPingPongBuffer(UInt8* destination, IOByteCount length); + + /// + /// Write to the ping pong buffer + /// + /// @param source The buffer to write + /// @param length The number of bytes to write (cannot exceed 512 bytes) + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_write_ppbuf()` defined in `rtsx_psr.c`. + /// + IOReturn writePingPongBuffer(const UInt8* source, IOByteCount length); + + // + // MARK: - Rx/Tx Phases + // + + /// + /// Get the clock phase for Rx + /// + /// @return The clock phase. + /// + ClockPhase getRxClockPhase(); + + /// + /// Get the clock phase for Tx + /// + /// @return The clock phase. + /// + ClockPhase getTxClockPhase(); + + // + // MARK: - Active State Power Management + // + + /// + /// Toggle the active state power management + /// + /// @param enable `true` if ASPM should be enabled, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_comm_set_aspm()` and `ops->set_aspm()` defined in `rtsx_pcr.c`. + /// + virtual IOReturn setASPM(bool enable); + + /// + /// Enable the active state power management + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `` + /// + inline IOReturn enableASPM() + { + return this->setASPM(true); + } + + /// + /// Disable the active state power management + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_disable_aspm()` defined in `rtsx_psr.c`. + /// + inline IOReturn disableASPM() + { + return this->setASPM(false); + } + + /// + /// Check whether the active state power management is active + /// + /// @return `true` if ASPM is enabled, `false` otherwise. + /// + inline bool isASPMEnabled() + { + using namespace RTSX::Chip; + + UInt8 value; + + if (this->readChipRegister(rAFCTL, value) != kIOReturnSuccess) + { + return true; + } + + return !BitOptions(value).contains(AFCTL::kCTL0 | AFCTL::kCTL1); + } + + /// + /// [Helper] Set the L1 off substates configuration + /// + /// @param value The register value of `L1SUB_CONFIG3` + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_set_l1off_sub()` defined in `rtsx_pcr.c`. + /// + inline IOReturn setL1OffSubConfig(UInt8 value) + { + return this->writeChipRegister(RTSX::Chip::L1SUB::rCFG3, 0xFF, value); + } + + /// + /// Set the L1 substates configuration + /// + /// @param active Pass `true` to set the active state + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_set_l1off_sub_cfg_d0()` and `set_l1off_cfg_sub_d0()` defined in `rtsx_pcr.c`. + /// @note The base controller provides a default implementation that simply returns `kIOReturnSuccess`. + /// + virtual IOReturn setL1OffSubConfigD0(bool active); + + /// + /// Set the LTR latency + /// + /// @param latency The latency + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_comm_set_ltr_latency()` and `rtsx_set_ltr_latency()` defined in `rtsx_psr.c`. + /// + IOReturn setLTRLatency(UInt32 latency); + + /// + /// Notify the card reader to enter into the worker state + /// + /// @note Port: This function replaces `rtsx_pci_start_run()`, `rtsx_pm_full_on()` and `rtsx_comm_pm_full_on()` defined in `rtsx_psr.c`. + /// + void enterWorkerState(); + + // + // MARK: - Hardware Interrupt Management + // + + /// + /// A filter that examines the interrupt event source + /// + /// @param source The interrupt event source + /// @return `true` if the interrupt is of interest, `false` otherwise. + /// @note The filter runs in the primary interrupt context. + /// + bool interruptEventSourceFilter(IOFilterInterruptEventSource* source); + + /// + /// Main interrupt service routine + /// + /// @param sender The interrupt event source + /// @param count The number of interrupts seen before delivery + /// @note The interrupt service routine runs in a gated context. + /// It checks hardware registers to identify the reason and invoke helper handlers. + /// @note The interrupt event source filter guarantees that there is a pending interrupt when the handler is invoked. + /// + void interruptHandlerGated(IOInterruptEventSource* sender, int count); + + /// + /// Timeout interrupt service routine + /// + /// @param sender The timer event source + /// @note The timeout handler runs in a gated context. + /// + void bufferTransferTimeoutHandlerGated(IOTimerEventSource* sender); + + /// + /// Helper interrupt service routine when a host command or data transfer is done + /// + /// @param succeeded `true` if the transfer has succeeded. `false` otherwise. + /// @note This interrupt service routine runs in a gated context. + /// + void onTransferDoneGated(bool succeeded); + + /// + /// Helper interrupt service routine when an overcurrent is detected + /// + /// @note This interrupt service routine runs in a gated context. + /// @note Port: This function replaces `rtsx_pci_process_ocp_interrupt()` and `rtsx_pci_process_ocp()` defined in `rtsx_psr.c`. + /// @note Unlike the Linux driver, this function is invoked if and only if OCP is enabled. + /// + virtual void onSDCardOvercurrentOccurredGated(); + + /// + /// Helper interrupt service routine when a SD card is inserted + /// + /// @note This interrupt service routine runs in a gated context. + /// @note Port: This function replaces `rtsx_pci_card_detect()` defined in `rtsx_psr.c` but has a completely different design and implementation. + /// + void onSDCardInsertedGated(); + + /// + /// Helper interrupt service routine when a SD card is removed + /// + /// @note This interrupt service routine runs in a gated context. + /// @note Port: This function replaces `rtsx_pci_card_detect()` defined in `rtsx_psr.c` but has a completely different design and implementation. + /// + void onSDCardRemovedGated(); + + // + // MARK: - Hardware Initialization and Configuration + // + + /// + /// Get the IC revision + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `*_get_ic_version()` defined in each controller file. + /// + virtual IOReturn getRevision(Revision& revision); + + /// + /// Optimize the physical layer + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `optimize_phy()` defined in the the `pcr->ops`. + /// + virtual IOReturn optimizePhys(); + + /// + /// Initialize hardware-specific parameters + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `*_init_params()` defined in each controller file. + /// + virtual IOReturn initParameters() = 0; + + /// + /// Initialize vendor-specific parameters + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `fetch_vendor_settings()` defined in the `pcr->ops`. + /// + virtual IOReturn initVendorSpecificParameters() = 0; + + /// + /// [Helper] Check whether the vendor settings register contains a valid value + /// + /// @param regVal The register value + /// @return `true` if valid, `false` otherwise. + /// + inline bool vsIsRegisterValueValid(UInt32 regVal) + { + return !(regVal & 0x1000000); + } + + /// + /// [Helper] Check whether the driver should send MMC commands during initialization + /// + /// @param regVal The register value + /// @return `true` if needed, `false` otherwise. + /// + inline bool vsIsMMCCommandsNeededDuringInitialiation(UInt32 regVal) + { + return !(regVal & 0x10); + } + + /// + /// [Helper] Extract the SD 3.0 card drive selector (1.8V) from the register value + /// + /// @param regVal The register value + /// @return The SD 3.0 card drive selector (1.8V). + /// + virtual UInt8 vsGetSD30DriveSelector1d8V(UInt32 regVal) + { + return (regVal >> 26) & 0x03; + } + + /// + /// [Helper] Extract the SD 3.0 card drive selector (3.3V) from the register value + /// + /// @param regVal The register value + /// @return The SD 3.0 card drive selector (3.3V). + /// + virtual UInt8 vsGetSD30DriveSelector3d3V(UInt32 regVal) + { + return (regVal >> 5) & 0x03; + } + + /// + /// [Helper] Extract the card drive selector from the register value + /// + /// @param regVal The register value + /// @return The card drive selector. + /// + virtual UInt8 vsGetCardDriveSelector(UInt32 regVal) + { + return ((regVal >> 25) & 0x01) << 6; + } + + /// + /// [Helper] Check whether the socket is reversed + /// + /// @param regVal The register value + /// @return `true` if the socket is reversed, `false` otherwise. + /// + inline bool vsIsSocketReversed(UInt32 regVal) + { + return regVal & 0x4000; + } + + /// + /// [Helper] Check whether the ASPM L1 is already enabled + /// + /// @param regVal The register value + /// @return `true` if L1 is enabled, `false` otherwise. + /// @note Port: This function replaces `rtsx_reg_to_aspm()` defined in `rtsx_pcr.h`. + /// + inline bool vsIsASPML1Enabled(UInt32 regVal) + { + return ((regVal >> 28) & 0x02) == 0x02; + } + + /// + /// Initialize the hardware (Extra Part) + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `extra_init_hw()` defined in defined in the `pcr->ops`. + /// + virtual IOReturn initHardwareExtra() = 0; + + /// + /// Initialize the hardware (Common Part) + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_init_hw()` defined in `rtsx_psr.c`. + /// + IOReturn initHardwareCommon(); + + // + // MARK: - Startup Routines + // + + /// + /// [Helper] Get the 8-bit base address register to map the device memory + /// + /// @return The BAR value 0x10 for all controllers other than RTS525A. + /// @note RTS525A controller must override this function and return 0x14 instead. + /// @note This helper function is invoked by `RealtekPCIeCardReaderController::mapDeviceMemory()`. + /// + virtual UInt8 getDeviceMemoryMapBaseAddressRegister() { return kIOPCIConfigBaseAddress0; } + + /// + /// Map the device memory + /// + /// @return `true` on success, `false` otherwise. + /// + bool mapDeviceMemory(); + + /// + /// Setup the workloop + /// + /// @return `true` on success, `false` otherwise. + /// + bool setupWorkLoop(); + + /// + /// [Helper] Probe the index of the message signaled interrupt + /// + /// @return The index on success, `-1` otherwise. + /// @note This helper function is invoked by `RealtekPCIeCardReaderController::setupInterrupts()`. + /// + int probeMSIIndex(); + + /// + /// Setup the interrupt management module + /// + /// @return `true` on success, `false` otherwise. + /// + bool setupInterrupts(); + + /// + /// Setup the host command and buffer management module + /// + /// @return `true` on success, `false` otherwise. + /// + bool setupHostBuffer(); + + /// + /// Setup the SD card reader + /// + /// @return `true` on success, `false` otherwise. + /// @note Port: This function replaces `rtsx_pci_init_chip()` defined in `rtsx_pci.c`. + /// + bool setupCardReader(); + + /// + /// Create the card slot and publish it + /// + /// @return `true` on success, `false` otherwise. + /// + bool createCardSlot(); + + // + // MARK: - Teardown Routines + // + + /// + /// Unmap the device memory + /// + void unmapDeviceMemory(); + + /// + /// Tear down the workloop + /// + void tearDownWorkLoop(); + + /// + /// Tear down the interrupt event source + /// + void tearDownInterrupts(); + + /// + /// Tear down the host command and buffer management module + /// + void tearDownHostBuffer(); + + /// + /// Destroy the card slot + /// + void destroyCardSlot(); + +public: + // + // MARK: - IOService Implementations + // + + /// + /// Start the controller + /// + /// @param provider An instance of PCI device that represents the card reader + /// @return `true` on success, `false` otherwise. + /// + bool start(IOService* provider) override; + + /// + /// Stop the controller + /// + /// @param provider An instance of PCI device that represents the card reader + /// + void stop(IOService* provider) override; +}; + +#endif /* RealtekPCIeCardReaderController_hpp */ diff --git a/RealtekPCIeCardReader/RealtekRTS5209Controller.cpp b/RealtekPCIeCardReader/RealtekRTS5209Controller.cpp new file mode 100644 index 0000000..1c3725a --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS5209Controller.cpp @@ -0,0 +1,8 @@ +// +// RealtekRTS5209Controller.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/17/21. +// + +#include "RealtekRTS5209Controller.hpp" diff --git a/RealtekPCIeCardReader/RealtekRTS5209Controller.hpp b/RealtekPCIeCardReader/RealtekRTS5209Controller.hpp new file mode 100644 index 0000000..599043b --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS5209Controller.hpp @@ -0,0 +1,13 @@ +// +// RealtekRTS5209Controller.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/17/21. +// + +#ifndef RealtekRTS5209Controller_hpp +#define RealtekRTS5209Controller_hpp + + + +#endif /* RealtekRTS5209Controller_hpp */ diff --git a/RealtekPCIeCardReader/RealtekRTS5249Controller.cpp b/RealtekPCIeCardReader/RealtekRTS5249Controller.cpp new file mode 100644 index 0000000..b38302e --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS5249Controller.cpp @@ -0,0 +1,167 @@ +// +// RealtekRTS5249Controller.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/27/21. +// + +#include "RealtekRTS5249Controller.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(RealtekRTS5249Controller, RealtekRTS5249SeriesController); + +// +// MARK: - Card Power Management +// + +/// +/// Switch to the given output voltage +/// +/// @param outputVoltage The new output voltage +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_switch_output_voltage()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekRTS5249Controller::switchOutputVoltage(OutputVoltage outputVoltage) +{ + using namespace RTSX::PHYS; + + PhysRegMaskValuePair pairs[] = + { + { rBACR, BACR::kBasicMask, 0 }, + { rTUNE, TUNE::kVoltageMask, TUNE::kD18_1V7 }, + }; + + IOReturn retVal; + + if (outputVoltage == OutputVoltage::k3d3V) + { + pairs[1].value = TUNE::kVoltage3V3; + + retVal = this->appendPhysRegisters(&pairs[1], 1); + } + else + { + retVal = this->appendPhysRegisters(pairs); + } + + if (retVal != kIOReturnSuccess) + { + perr("Failed to inform the hardware the new output voltage. Error = 0x%x.", retVal); + + return retVal; + } + + return this->setDrivingForOutputVoltage(outputVoltage, false, 100); +} + +// +// MARK: - Hardware Initialization and Configuration +// + +/// +/// Optimize the physical layer +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn RealtekRTS5249Controller::optimizePhys() +{ + pinfo("Optimizing the physical layer..."); + + using namespace RTSX::Chip; + + pinfo("Enabling the D3 delink mode..."); + + IOReturn retVal = this->writeChipRegister(PM::rCTRL3, PM::CTRL3::kEnableD3DelinkMode, 0x00); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable D3 delink mode. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("D3 delink mode has been enabled."); + + using namespace RTSX::PHYS; + + retVal = this->writePhysRegister(rREV, REV::value5249()); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to optimize the physical register REV. Error = 0x%x.", retVal); + + return retVal; + } + + IOSleep(1); + + const PhysRegValuePair pairs[] = + { + { rBPCR, BPCR::kIBRxSel | BPCR::kIBTxSel | BPCR::kIBFilter | BPCR::kEnableCMirror }, + { rPCR, PCR::kForceCode | PCR::kOOBSCali50 | PCR::kOOBSVCM08 | PCR::kOOBSSEN90 | PCR::kEnableRSSI | PCR::kRx10K }, + { rRCR2, RCR2::kEnableEmphase | RCR2::kNADJR | RCR2::kCDRSR2 | RCR2::kFreqSel12 | RCR2::kCDRSC12P | RCR2::kCalibLate }, + { rFLD4, FLD4::kFLDENSel | FLD4::kReqRef | FLD4::kRxAmpOff | FLD4::kReqADDA | FLD4::kBerCount | FLD4::kBerTimer | FLD4::kBerCheckEnable }, + { rRDR, RDR::kRxDSEL19 | RDR::kSSCAutoPwd }, + { rRCR1, RCR1::kADPTime4 | RCR1::kVCOCoarse }, + { rFLD3, FLD3::kTimer4 | FLD3::kTimer6 | FLD3::kRxDelink }, + { rTUNE, TUNE::kTuneRef10 | TUNE::kVBGSel1252 | TUNE::kSDBus33 | TUNE::kTuneD18 | TUNE::kTuneD12 | TUNE::kTuneA12 }, + }; + + return this->writePhysRegisters(pairs); +} + +/// +/// [Helper] Get a sequence of registers needed to initialize the hardware +/// +/// @param pairs An array of registers to be populated +/// @return The number of registers in the array. +/// +IOItemCount RealtekRTS5249Controller::initHardwareExtraGetChipRegValuePairs(ChipRegValuePair (&pairs)[64]) +{ + using namespace RTSX::Chip; + + IOItemCount count = 0; + + auto populate = [&](UInt16 address, UInt8 mask, UInt8 value) -> void + { + pairs[count] = { address, mask, value }; + + count += 1; + }; + + // COMM: Reset the L1SUB config + populate(L1SUB::rCFG3, 0xFF, 0x00); + + // COMM: Turn on the LED + populate(GPIO::rCTL, GPIO::CTL::kLEDMask, GPIO::CTL::kTurnOnLEDValue); + + // COMM: Reset the ASPM state + populate(rAFCTL, 0x3F, 0x00); + + // COMM: Switch the LDO3318 source from DV33 to Card33 + populate(LDO::rPWRSEL, 0x03, 0x00); + populate(LDO::rPWRSEL, 0x03, 0x01); + + // COMM: Set initial LED cycle period + populate(OLT_LED::rCTL, 0x0F, 0x02); + + // COMM: Set reversed socket + populate(rPETXCFG, 0xB0, this->parameters.isSocketReversed ? 0xB0 : 0x80); + + // SPEC: Disable PM wake + populate(PM::rCTRL3, PM::CTRL3::kEnablePmWake, 0x00); + + // SPEC: Disable D3 delink mode + populate(PM::rCTRL3, PM::CTRL3::kEnableD3DelinkMode, 0x00); + + // SPEC: ??? + populate(rPFCTL, 0xFF, 0x30); + + // COMM: Request clock + populate(rPETXCFG, PETXCFT::kForceClockRequestDelinkMask, this->parameters.pm.forceClockRequest ? PETXCFT::kForceClockRequestLow : PETXCFT::kForceClockRequestHigh); + + return count; +} diff --git a/RealtekPCIeCardReader/RealtekRTS5249Controller.hpp b/RealtekPCIeCardReader/RealtekRTS5249Controller.hpp new file mode 100644 index 0000000..0174bcc --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS5249Controller.hpp @@ -0,0 +1,56 @@ +// +// RealtekRTS5249Controller.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/27/21. +// + +#ifndef RealtekRTS5249Controller_hpp +#define RealtekRTS5249Controller_hpp + +#include "RealtekRTS5249SeriesController.hpp" + +/// +/// Represents the RTS5249 card reader controller +/// +class RealtekRTS5249Controller: public RealtekRTS5249SeriesController +{ + // MARK: - Constructors & Destructors + OSDeclareDefaultStructors(RealtekRTS5249Controller); + + using super = RealtekRTS5249SeriesController; + + // + // MARK: - Card Power Management + // + + /// + /// Switch to the given output voltage + /// + /// @param outputVoltage The new output voltage + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_switch_output_voltage()` defined in `rtsx_psr.c`. + /// + IOReturn switchOutputVoltage(OutputVoltage outputVoltage) override; + + // + // MARK: - Hardware Initialization and Configuration + // + + /// + /// Optimize the physical layer + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + virtual IOReturn optimizePhys() override; + + /// + /// [Helper] Get a sequence of registers needed to initialize the hardware + /// + /// @param pairs An array of registers to be populated + /// @return The number of registers in the array. + /// + IOItemCount initHardwareExtraGetChipRegValuePairs(ChipRegValuePair (&pairs)[64]) override; +}; + +#endif /* RealtekRTS5249Controller_hpp */ diff --git a/RealtekPCIeCardReader/RealtekRTS5249SeriesController.cpp b/RealtekPCIeCardReader/RealtekRTS5249SeriesController.cpp new file mode 100644 index 0000000..0192c95 --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS5249SeriesController.cpp @@ -0,0 +1,512 @@ +// +// RealtekRTS5249SeriesController.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/16/21. +// + +#include "RealtekRTS5249SeriesController.hpp" +#include "Registers.hpp" +#include "BitOptions.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndAbstractStructors(RealtekRTS5249SeriesController, RealtekPCIeCardReaderController); + +// +// MARK: - Driving Tables for RTS5249, 524A, 525A +// + +/// SD 3.0 drive table (1.8V) +const RealtekRTS5249SeriesController::DrivingTable RealtekRTS5249SeriesController::kSD30DriveTable1d8V = +{{ + {0xC4, 0xC4, 0xC4}, + {0x3C, 0x3C, 0x3C}, + {0xFE, 0xFE, 0xFE}, + {0xB3, 0xB3, 0xB3}, +}}; + +/// SD 3.0 drive table (3.3V) +const RealtekRTS5249SeriesController::DrivingTable RealtekRTS5249SeriesController::kSD30DriveTable3d3V = +{{ + {0x11, 0x11, 0x18}, + {0x55, 0x55, 0x5C}, + {0xFF, 0xFF, 0xFF}, + {0x96, 0x96, 0x96}, +}}; + +// +// MARK: - SD Pull Control Tables +// + +/// A sequence of registers to transfer to enable SD pull control +const ChipRegValuePair RealtekRTS5249SeriesController::kSDEnablePullControlTablePairs[] = +{ + { RTSX::Chip::CARD::PULL::rCTL1, 0x66 }, + { RTSX::Chip::CARD::PULL::rCTL2, 0xAA }, + { RTSX::Chip::CARD::PULL::rCTL3, 0xE9 }, + { RTSX::Chip::CARD::PULL::rCTL4, 0xAA }, +}; + +const SimpleRegValuePairs RealtekRTS5249SeriesController::kSDEnablePullControlTable = +{ + RealtekRTS5249SeriesController::kSDEnablePullControlTablePairs +}; + +/// A sequence of registers to transfer to disable SD pull control +const ChipRegValuePair RealtekRTS5249SeriesController::kSDDisablePullControlTablePairs[] = +{ + { RTSX::Chip::CARD::PULL::rCTL1, 0x66 }, + { RTSX::Chip::CARD::PULL::rCTL2, 0x55 }, + { RTSX::Chip::CARD::PULL::rCTL3, 0xD5 }, + { RTSX::Chip::CARD::PULL::rCTL4, 0x55 }, +}; + +const SimpleRegValuePairs RealtekRTS5249SeriesController::kSDDisablePullControlTable = +{ + RealtekRTS5249SeriesController::kSDDisablePullControlTablePairs +}; + +// +// MARK: - LED Management +// + +/// +/// Turn on the LED +/// +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `turn_on_led()` defined in `struct pcr_ops`. +/// +IOReturn RealtekRTS5249SeriesController::turnOnLED() +{ + using namespace RTSX::Chip::GPIO; + + return this->writeChipRegister(rCTL, CTL::kLEDMask, CTL::kTurnOnLEDValue); +} + +/// +/// Turn off the LED +/// +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `turn_off_led()` defined in `struct pcr_ops`. +/// +IOReturn RealtekRTS5249SeriesController::turnOffLED() +{ + using namespace RTSX::Chip::GPIO; + + return this->writeChipRegister(rCTL, CTL::kLEDMask, CTL::kTurnOffLEDValue); +} + +/// +/// Enable LED blinking +/// +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `enable_auto_blink()` defined in `struct pcr_ops`. +/// +IOReturn RealtekRTS5249SeriesController::enableLEDBlinking() +{ + using namespace RTSX::Chip::OLT_LED; + + return this->writeChipRegister(rCTL, CTL::kBlinkingMask, CTL::kEnableBlinkingValue); +} + +/// +/// Disable LED blinking +/// +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `enable_auto_blink()` defined in `struct pcr_ops`. +/// +IOReturn RealtekRTS5249SeriesController::disableLEDBlinking() +{ + using namespace RTSX::Chip::OLT_LED; + + return this->writeChipRegister(rCTL, CTL::kBlinkingMask, CTL::kDisableBlinkingValue); +} + +// +// MARK: - Card Power Management +// + +/// +/// Power on the SD card +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_card_power_on()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekRTS5249SeriesController::powerOnCard() +{ + using namespace RTSX::Chip; + + IOReturn retVal; + + // Enable the overcurrent protection + if (this->parameters.ocp.enable) + { + pinfo("Enabling the overcurrent protection..."); + + retVal = this->enableOvercurrentProtection(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable the overcurrent protection. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The overcurrent protection has been enabled."); + } + + // Enable the card power in two phases + // Phase 1: Partial Power + pinfo("Powering on the card partially..."); + + const ChipRegValuePair ppairs[] = + { + { CARD::rPWRCTRL, CARD::PWRCTRL::kSDPowerMask, CARD::PWRCTRL::kSDVCCPartialPowerOn }, + { rPWRGATECTRL, PWRGATECTRL::kMask, PWRGATECTRL::kVCC1 } + }; + + retVal = this->transferWriteRegisterCommands(SimpleRegValuePairs(ppairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to partially power the card. Error = 0x%x.", retVal); + + return retVal; + } + + IOSleep(5); + + pinfo("The card power is partially on."); + + // Phase 2: Full Power + const ChipRegValuePair fpairs[] = + { + { CARD::rPWRCTRL, CARD::PWRCTRL::kSDPowerMask, CARD::PWRCTRL::kSDVCCPowerOn }, + { rPWRGATECTRL, PWRGATECTRL::kMask, PWRGATECTRL::kVCC1 | PWRGATECTRL::kVCC2 } + }; + + retVal = this->transferWriteRegisterCommands(SimpleRegValuePairs(fpairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to fully power on the card. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The card power is fully on."); + + return kIOReturnSuccess; +} + +/// +/// Power off the SD card +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_card_power_off()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekRTS5249SeriesController::powerOffCard() +{ + using namespace RTSX::Chip; + + // Disable the overcurrent protection + if (this->parameters.ocp.enable) + { + psoftassert(this->disableOvercurrentProtection() == kIOReturnSuccess, + "Failed to disable the overcurrent protection."); + } + + // Power off the card + const ChipRegValuePair pairs[] = + { + { CARD::rPWRCTRL, CARD::PWRCTRL::kSDPowerMask, CARD::PWRCTRL::kSDPowerOff }, + { rPWRGATECTRL, PWRGATECTRL::kMask, PWRGATECTRL::kOn } + }; + + return this->transferWriteRegisterCommands(SimpleRegValuePairs(pairs)); +} + +// +// MARK: - Hardware Initialization and Configuration +// + +/// +/// Initialize hardware-specific parameters +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `*_init_params()` defined in each controller file. +/// +IOReturn RealtekRTS5249SeriesController::initParameters() +{ + pinfo("Initializing the device-specific parameters..."); + + IOReturn retVal = this->getRevision(this->parameters.revision); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to get the IC revision. Error = 0x%x.", retVal); + + return retVal; + } + + this->parameters.numSlots = 2; + + this->parameters.caps.supportsSDSDR50 = true; + + this->parameters.caps.supportsSDSDR104 = true; + + this->parameters.isSocketReversed = false; + + this->parameters.cardDriveSelector = RTSX::Chip::CARD::DRVSEL::kDefault; + + this->parameters.sd30DriveSelector1d8V = RTSX::Chip::CARD::SD30::DRVSEL::CFG::kDriverTypeB; + + this->parameters.sd30DriveSelector3d3V = RTSX::Chip::CARD::SD30::DRVSEL::CFG::kDriverTypeB; + + this->parameters.sd30DriveTable1d8V = &RealtekRTS5249SeriesController::kSD30DriveTable1d8V; + + this->parameters.sd30DriveTable3d3V = &RealtekRTS5249SeriesController::kSD30DriveTable3d3V; + + this->parameters.sdEnablePullControlTable = &RealtekRTS5249SeriesController::kSDEnablePullControlTable; + + this->parameters.sdDisablePullControlTable = &RealtekRTS5249SeriesController::kSDDisablePullControlTable; + + this->parameters.initialTxClockPhase = {1, 29, 16}; + + this->parameters.initialRxClockPhase = {24, 6, 5}; + + this->parameters.ocp.enable = false; + + this->parameters.pm.isASPML1Enabled = true; + + this->parameters.pm.enableLTRL1SSPowerGateCheckCard = true; + + this->parameters.pm.enableLTRL1SSPowerGate = true; + + this->parameters.pm.enableLTRMode = true; + + this->parameters.pm.ltrActiveLatency = LTR_ACTIVE_LATENCY_DEF; + + this->parameters.pm.ltrIdleLatency = LTR_IDLE_LATENCY_DEF; + + this->parameters.pm.ltrL1OffLatency = LTR_L1OFF_LATENCY_DEF; + + this->parameters.pm.ltrSnoozeDelay = L1_SNOOZE_DELAY_DEF; + + this->parameters.pm.ltrL1OffSSPowerGate = LTR_L1OFF_SSPWRGATE_5249_DEF; + + this->parameters.pm.ltrL1OffSnoozeSSPowerGate = LTR_L1OFF_SNOOZE_SSPWRGATE_5249_DEF; + + pinfo("Device-specific parameters have been initialized."); + + return kIOReturnSuccess; +} + +/// +/// Initialize vendor-specific parameters +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `fetch_vendor_settings()` defined in the `pcr->ops`. +/// +IOReturn RealtekRTS5249SeriesController::initVendorSpecificParameters() +{ + pinfo("Initializing the vendor-specific parameters..."); + + // PCR Config 1 + UInt32 regVal = this->device->configRead32(RTSX::PCR::kSREG1); + + if (!this->vsIsRegisterValueValid(regVal)) + { + perr("Vendor settings are invalid."); + + return kIOReturnError; + } + + this->parameters.pm.isASPML1Enabled = this->vsIsASPML1Enabled(regVal); + + this->parameters.sd30DriveSelector1d8V = this->vsGetSD30DriveSelector1d8V(regVal); + + this->parameters.cardDriveSelector &= 0x3F; + + this->parameters.cardDriveSelector |= this->vsGetCardDriveSelector(regVal); + + pinfo("PCR CFG1: RegVal = 0x%08x.", regVal); + + pinfo("PCR CFG1: ASPM L1 Enabled: %s.", YESNO(this->parameters.pm.isASPML1Enabled)); + + pinfo("PCR CFG1: SD30 Drive Selector (1.8V) = %d.", this->parameters.sd30DriveSelector1d8V); + + pinfo("PCR CFG1: SD Card Drive Selector = 0x%x.", this->parameters.cardDriveSelector); + + // PCR Config 2 + regVal = this->device->configRead32(RTSX::PCR::kSREG2); + + this->parameters.caps.noMMCCommandsDuringInitialization = !this->vsIsMMCCommandsNeededDuringInitialiation(regVal); + + this->parameters.sd30DriveSelector3d3V = this->vsGetSD30DriveSelector3d3V(regVal); + + this->parameters.isSocketReversed = this->vsIsSocketReversed(regVal); + + pinfo("PCR CFG2: RegVal = 0x%08x.", regVal); + + pinfo("PCR CFG2: SD30 Drive Selector (3.3V) = %d.", this->parameters.sd30DriveSelector3d3V); + + pinfo("PCR CFG2: Socket Reversed = %s.", YESNO(this->parameters.isSocketReversed)); + + pinfo("Vendor-specific parameters have been initialized."); + + return kIOReturnSuccess; +} + +/// +/// [Helper] Initialize the hardware from the PCI config +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rts5249_init_from_cfg()` and `rts5249_init_from_hw()` defined in `rts5249.c`. +/// +IOReturn RealtekRTS5249SeriesController::initHardwareFromConfig() +{ + pinfo("Initializing the hardware from the PCIe config."); + + UInt32 l1ss = this->device->extendedFindPCICapability(static_cast(kIOPCIExpressCapabilityIDL1PMSubstates)); + + if (l1ss == 0) + { + pwarning("Skipped: L1SS is not available.") + + return kIOReturnSuccess; + } + + BitOptions regVal = this->device->configRead32(PCI_L1SS_CTL1 + l1ss); + + pinfo("L1SS RegVal = 0x%08x.", regVal.flatten()); + + if (this->supportsOOBSPolling()) + { + pinfo("Configuring the OOBS polling..."); + + IOReturn retVal; + + if (!regVal.contains(PCI_L1SS_CTL1_L1SS_MASK)) + { + pinfo("Enabling the OOBS polling..."); + + retVal = this->enableOOBSPolling(); + } + else + { + pinfo("Disabling the OOBS polling..."); + + retVal = this->disableOOBSPolling(); + } + + if (retVal != kIOReturnSuccess) + { + perr("Failed to configure OOBS polling. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("OOBS polling has been configured."); + } + + pinfo("Setting the ASPM..."); + + this->parameters.pm.enableASPML11 = regVal.contains(PCI_L1SS_CTL1_ASPM_L1_1); + + this->parameters.pm.enableASPML12 = regVal.contains(PCI_L1SS_CTL1_ASPM_L1_2); + + this->parameters.pm.enablePCIPML11 = regVal.contains(PCI_L1SS_CTL1_PCIPM_L1_1); + + this->parameters.pm.enablePCIPML12 = regVal.contains(PCI_L1SS_CTL1_PCIPM_L1_2); + + if (this->parameters.pm.enableLTRMode) + { + if (BitOptions(IOPCIeDeviceConfigRead16(this->device, PCI_EXP_DEVCTL2)).contains(PCI_EXP_DEVCTL2_LTR_EN)) + { + this->parameters.pm.isLTRModeEnabled = true; + + this->parameters.pm.isLTRModeActive = true; + + psoftassert(this->setLTRLatency(this->parameters.pm.ltrActiveLatency) == kIOReturnSuccess, + "Failed to set the LTR latency for the active mode."); + + pinfo("LTR mode is enabled and active."); + } + else + { + this->parameters.pm.isLTRModeEnabled = false; + + pinfo("LTR mode is disabled."); + } + } + + this->parameters.pm.forceClockRequest = !(this->parameters.pm.enableASPML11 && + this->parameters.pm.enableASPML12 && + this->parameters.pm.enablePCIPML11 && + this->parameters.pm.enablePCIPML12); + + pinfo("Force Clock Request = %s.", YESNO(this->parameters.pm.forceClockRequest)); + + pinfo("Initialized the hardware from the PCIe config."); + + return kIOReturnSuccess; +} + +/// +/// Initialize the hardware (Extra Part) +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `extra_init_hw()` defined in defined in the `pcr->ops`. +/// +IOReturn RealtekRTS5249SeriesController::initHardwareExtra() +{ + pinfo("Initializing the card reader (device-specific)..."); + + // Initialize power management settings + IOReturn retVal = this->initHardwareFromConfig(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initialize the hardware from the PCI config. Error = 0x%x.", retVal); + + return retVal; + } + + // Set up registers that are needed to initialize the hardware + pinfo("Configuring the card reader chip (device-specific)..."); + + ChipRegValuePair pairs[64] = {}; + + IOItemCount count = this->initHardwareExtraGetChipRegValuePairs(pairs); + + retVal = this->transferWriteRegisterCommands(SimpleRegValuePairs(pairs, count)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to write registers that are needed to initialize the hardware. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The card reader chip has been configured (device-specific)."); + + // Set up the driving for 3.3V output voltage + pinfo("Setting the driving table for the 3.3V output.") + + retVal = this->setDrivingForOutputVoltage(OutputVoltage::k3d3V, false, 100); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the driving for 3.3V output voltage. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Initialized the card reader (device-specific)."); + + return kIOReturnSuccess; +} diff --git a/RealtekPCIeCardReader/RealtekRTS5249SeriesController.hpp b/RealtekPCIeCardReader/RealtekRTS5249SeriesController.hpp new file mode 100644 index 0000000..14a7e73 --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS5249SeriesController.hpp @@ -0,0 +1,149 @@ +// +// RealtekRTS5249SeriesController.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/16/21. +// + +#ifndef RealtekRTS5249SeriesController_hpp +#define RealtekRTS5249SeriesController_hpp + +#include "RealtekPCIeCardReaderController.hpp" + +/// +/// Represents the abstract RTS5249 series card reader controller +/// +class RealtekRTS5249SeriesController: public RealtekPCIeCardReaderController +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareAbstractStructors(RealtekRTS5249SeriesController); + + using super = RealtekPCIeCardReaderController; + + // + // MARK: - Driving Tables for RTS5249, 524A, 525A + // + + /// SD 3.0 drive table (1.8V) + static const DrivingTable kSD30DriveTable1d8V; + + /// SD 3.0 drive table (3.3V) + static const DrivingTable kSD30DriveTable3d3V; + + // + // MARK: - SD Pull Control Tables + // + + /// A sequence of chip registers to enable SD pull control + static const ChipRegValuePair kSDEnablePullControlTablePairs[]; + static const SimpleRegValuePairs kSDEnablePullControlTable; + + /// A sequence of chip registers to disable SD pull control + static const ChipRegValuePair kSDDisablePullControlTablePairs[]; + static const SimpleRegValuePairs kSDDisablePullControlTable; + + // + // MARK: - LED Management + // + + /// + /// Turn on the LED + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `turn_on_led()` defined in `struct pcr_ops`. + /// + IOReturn turnOnLED() override; + + /// + /// Turn off the LED + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `turn_off_led()` defined in `struct pcr_ops`. + /// + IOReturn turnOffLED() override; + + /// + /// Enable LED blinking + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `enable_auto_blink()` defined in `struct pcr_ops`. + /// + IOReturn enableLEDBlinking() override; + + /// + /// Disable LED blinking + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `enable_auto_blink()` defined in `struct pcr_ops`. + /// + IOReturn disableLEDBlinking() override; + + // + // MARK: - Card Power Management + // + + /// + /// Power on the SD card + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_card_power_on()` defined in `rtsx_psr.c`. + /// + IOReturn powerOnCard() override; + + /// + /// Power off the SD card + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_card_power_off()` defined in `rtsx_psr.c`. + /// + IOReturn powerOffCard() override; + + // + // MARK: - Hardware Initialization and Configuration + // + + /// + /// Initialize hardware-specific parameters + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `*_init_params()` defined in each controller file. + /// + virtual IOReturn initParameters() override; + + /// + /// Initialize vendor-specific parameters + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `fetch_vendor_settings()` defined in the `pcr->ops`. + /// + virtual IOReturn initVendorSpecificParameters() override; + + /// + /// [Helper] Initialize the hardware from the PCI config + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rts5249_init_from_cfg()` and `rts5249_init_from_hw()` defined in `rts5249.c`. + /// + IOReturn initHardwareFromConfig(); + + /// + /// Initialize the hardware (Extra Part) + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `extra_init_hw()` defined in defined in the `pcr->ops`. + /// + virtual IOReturn initHardwareExtra() override; + + /// + /// [Helper] Get a sequence of registers needed to initialize the hardware + /// + /// @param pairs An array of registers to be populated + /// @return The number of registers in the array. + /// + virtual IOItemCount initHardwareExtraGetChipRegValuePairs(ChipRegValuePair (&pairs)[64]) = 0; +}; + +#endif /* RealtekRTS5249SeriesController_hpp */ diff --git a/RealtekPCIeCardReader/RealtekRTS524AController.cpp b/RealtekPCIeCardReader/RealtekRTS524AController.cpp new file mode 100644 index 0000000..4deb8af --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS524AController.cpp @@ -0,0 +1,241 @@ +// +// RealtekRTS524AController.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/18/21. +// + +#include "RealtekRTS524AController.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(RealtekRTS524AController, RealtekRTS5249SeriesController); + +// +// MARK: - Access Physical Layer Registers (Default, Overridable) +// + +/// +/// Read a word from the physical layer register at the given address +/// +/// @param address The register address +/// @param value The register value on return +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_read_phy_register()` defined in `rtsx_psr.c`. +/// @note Subclasses may override this function to provide a device-specific implementation. +/// +IOReturn RealtekRTS524AController::readPhysRegister(UInt8 address, UInt16& value) +{ + if ((address & 0x80) != 0) + { + address &= 0x7F; + + address |= 0x40; + } + + return super::readPhysRegister(address, value); +} + +/// +/// Write a word to the physical layer register at the given address +/// +/// @param address The register address +/// @param value The register value +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. +/// @note Port: This function replaces `rtsx_pci_write_phy_register()` defined in `rtsx_psr.c`. +/// @note Subclasses may override this function to provide a device-specific implementation. +/// +IOReturn RealtekRTS524AController::writePhysRegister(UInt8 address, UInt16 value) +{ + if ((address & 0x80) != 0) + { + address &= 0x7F; + + address |= 0x40; + } + + return super::writePhysRegister(address, value); +} + +// +// MARK: - Card Power Management +// + +/// +/// Switch to the given output voltage +/// +/// @param outputVoltage The new output voltage +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_switch_output_voltage()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekRTS524AController::switchOutputVoltage(OutputVoltage outputVoltage) +{ + using namespace RTSX::PHYS; + + IOReturn retVal; + + if (outputVoltage == OutputVoltage::k3d3V) + { + retVal = this->appendPhysRegister(rTUNE, TUNE::kVoltageMask, TUNE::kVoltage3V3); + } + else + { + retVal = this->appendPhysRegister(rTUNE, TUNE::kVoltageMask, TUNE::kD18_1V8); + } + + if (retVal != kIOReturnSuccess) + { + perr("Failed to inform the hardware the new output voltage. Error = 0x%x.", retVal); + + return retVal; + } + + return this->setDrivingForOutputVoltage(outputVoltage, false, 100); +} + +// +// MARK: - Hardware Initialization and Configuration +// + +/// +/// Optimize the physical layer +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn RealtekRTS524AController::optimizePhys() +{ + pinfo("Optimizing the physical layer..."); + + using namespace RTSX::Chip; + + pinfo("Enabling the D3 delink mode..."); + + IOReturn retVal = this->writeChipRegister(PM::rCTRL3_52XA, PM::CTRL3::kEnableD3DelinkMode, 0x00); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable D3 delink mode. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("D3 delink mode has been enabled."); + + using namespace RTSX::PHYS; + + const PhysRegValuePair kDefault[] = + { + { rPCR, PCR::kForceCode | PCR::kOOBSCali50 | PCR::kOOBSVCM08 | PCR::kOOBSSEN90 | PCR::kEnableRSSI }, + { rSSCCR3, SSCCR3::kStepIn | SSCCR3::kCheckDelay }, + { rANA8, ANA8::kRxEQDCGain | ANA8::kSelRxEnable | ANA8::kRxEQValue | ANA8::kSCP | ANA8::kSelIPI }, + }; + + const PhysRegValuePair kRevA[] = + { + { rPCR, PCR::kForceCode | PCR::kOOBSCali50 | PCR::kOOBSVCM08 | PCR::kOOBSSEN90 | PCR::kEnableRSSI }, + { rSSCCR3, SSCCR3::kStepIn | SSCCR3::kCheckDelay }, + { rSSCCR2, SSCCR2::kPLLNcode | SSCCR2::kTime0 | SSCCR2::kTime2Width }, + { rANA1A, ANA1A::kTXRLoopback | ANA1A::kRXTBist | ANA1A::kTXRBist | ANA1A::kRev }, + { rANA1D, ANA1D::kDebugAddress }, + { rDIG1E, DIG1E::kRev | DIG1E::kD0XD1 | DIG1E::kRxOnHost | DIG1E::kRCLKRefHost | DIG1E::kRCLKTxEnableKeep | DIG1E::kRCLKTxTermKeep | DIG1E::kRCLKRxEIdleOn | DIG1E::kTxTermKeep | DIG1E::kRxTermKeep | DIG1E::kTxEnableKeep | DIG1E::kRxEnableKeep }, + { rANA8, ANA8::kRxEQDCGain | ANA8::kSelRxEnable | ANA8::kRxEQValue | ANA8::kSCP | ANA8::kSelIPI }, + }; + + if (this->parameters.revision == Revision::kA) + { + return this->writePhysRegisters(kRevA); + } + else + { + return this->writePhysRegisters(kDefault); + } +} + +/// +/// [Helper] Get a sequence of registers needed to initialize the hardware +/// +/// @param pairs An array of registers to be populated +/// @return The number of registers in the array. +/// +IOItemCount RealtekRTS524AController::initHardwareExtraGetChipRegValuePairs(ChipRegValuePair (&pairs)[64]) +{ + using namespace RTSX::Chip; + + IOItemCount count = 0; + + auto populate = [&](UInt16 address, UInt8 mask, UInt8 value) -> void + { + pairs[count] = { address, mask, value }; + + count += 1; + }; + + // COMM: Reset the L1SUB config + populate(L1SUB::rCFG3, 0xFF, 0x00); + + // COMM: Turn on the LED + populate(GPIO::rCTL, GPIO::CTL::kLEDMask, GPIO::CTL::kTurnOnLEDValue); + + // COMM: Reset the ASPM state + populate(rAFCTL, 0x3F, 0x00); + + // COMM: Switch the LDO3318 source from DV33 to Card33 + populate(LDO::rPWRSEL, 0x03, 0x00); + populate(LDO::rPWRSEL, 0x03, 0x01); + + // COMM: Set initial LED cycle period + populate(OLT_LED::rCTL, 0x0F, 0x02); + + // COMM: Set reversed socket + populate(rPETXCFG, 0xB0, this->parameters.isSocketReversed ? 0xB0 : 0x80); + + // SPEC: ??? + populate(rVREF, VREF::kEnablePwdSuspnd, VREF::kEnablePwdSuspnd); + + // SPEC: Disable PM wake + populate(PM::rCTRL3_52XA, PM::CTRL3::kEnablePmWake, 0x00); + + // SPEC: Disable D3 delink mode + populate(PM::rCTRL3_52XA, PM::CTRL3::kEnableD3DelinkMode, 0x00); + + // SPEC: ??? + populate(rPFCTL_52XA, 0x30, 0x20); + + // COMM: Request clock + populate(rPETXCFG, PETXCFT::kForceClockRequestDelinkMask, this->parameters.pm.forceClockRequest ? PETXCFT::kForceClockRequestLow : PETXCFT::kForceClockRequestHigh); + + // SPEC: Power off the EFUSE + populate(rPFCTL_52XA, PFCTL_52XA::kEfusePowerMask, PFCTL_52XA::kEfusePowerOff); + + // SPEC: ??? (Differs from 525A) + populate(rFCTL, AFCTL::kEnableASPML1, AFCTL::kEnableASPML1); + + // SPEC: Debug power management events + populate(rPEDBG, PEDBG::kDebug0, PEDBG::kDebug0); + + // SPEC: LDO VCC + populate(LDO::VCC::rCFG1, LDO::VCC::CFG1::kLimitEnable, LDO::VCC::CFG1::kLimitEnable); + + // SPEC: ??? + populate(rPCLKCTL, PCKLCTL::kModeSelector, PCKLCTL::kModeSelector); + + // SPEC: Revision A + if (this->parameters.revision == Revision::kA) + { + populate(LDO::DV18::rCFG, LDO::DV18::CFG::kSRMask, LDO::DV18::CFG::kSRDefault); + + populate(LDO::VCC::rCFG1, LDO::VCC::CFG1::kRefTuneMask, LDO::VCC::CFG1::kRef1d2V); + + populate(LDO::VIO::rCFG, LDO::VIO::CFG::kRefTuneMask, LDO::VIO::CFG::kRef1d2V); + + populate(LDO::VIO::rCFG, LDO::VIO::CFG::kSRMask, LDO::VIO::CFG::kSRDefault); + + populate(LDO::DV12S::rCFG, LDO::DV12S::CFG::kRef12TuneMask, LDO::DV12S::CFG::kRef12TuneDefault); + + populate(LDO::rCTL1, LDO::CTL1::kSD40VIOTuneMask, LDO::CTL1::kSD40VIOTune1V7); + } + + return count; +} diff --git a/RealtekPCIeCardReader/RealtekRTS524AController.hpp b/RealtekPCIeCardReader/RealtekRTS524AController.hpp new file mode 100644 index 0000000..ab8864f --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS524AController.hpp @@ -0,0 +1,82 @@ +// +// RealtekRTS524AController.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 6/18/21. +// + +#ifndef RealtekRTS524AController_hpp +#define RealtekRTS524AController_hpp + +#include "RealtekRTS5249SeriesController.hpp" + +/// +/// Represents the RTS524A card reader controller +/// +class RealtekRTS524AController: public RealtekRTS5249SeriesController +{ + // MARK: - Constructors & Destructors + OSDeclareDefaultStructors(RealtekRTS524AController); + + using super = RealtekRTS5249SeriesController; + + // + // MARK: - Access Physical Layer Registers (Default, Overridable) + // + + /// + /// Read a word from the physical layer register at the given address + /// + /// @param address The register address + /// @param value The register value on return + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_read_phy_register()` defined in `rtsx_psr.c`. + /// @note Subclasses may override this function to provide a device-specific implementation. + /// + IOReturn readPhysRegister(UInt8 address, UInt16& value) override; + + /// + /// Write a word to the physical layer register at the given address + /// + /// @param address The register address + /// @param value The register value + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, `kIOReturnError` otherwise. + /// @note Port: This function replaces `rtsx_pci_write_phy_register()` defined in `rtsx_psr.c`. + /// @note Subclasses may override this function to provide a device-specific implementation. + /// + IOReturn writePhysRegister(UInt8 address, UInt16 value) override; + + // + // MARK: - Card Power Management + // + + /// + /// Switch to the given output voltage + /// + /// @param outputVoltage The new output voltage + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_switch_output_voltage()` defined in `rtsx_psr.c`. + /// + IOReturn switchOutputVoltage(OutputVoltage outputVoltage) override; + + // + // MARK: - Hardware Initialization and Configuration + // + + /// + /// Optimize the physical layer + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + virtual IOReturn optimizePhys() override; + + /// + /// [Helper] Get a sequence of registers needed to initialize the hardware + /// + /// @param pairs An array of registers to be populated + /// @return The number of registers in the array. + /// + IOItemCount initHardwareExtraGetChipRegValuePairs(ChipRegValuePair (&pairs)[64]) override; +}; + +#endif /* RealtekRTS524AController_hpp */ diff --git a/RealtekPCIeCardReader/RealtekRTS525AController.cpp b/RealtekPCIeCardReader/RealtekRTS525AController.cpp new file mode 100644 index 0000000..0ac8e87 --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS525AController.cpp @@ -0,0 +1,328 @@ +// +// RealtekRTS525AController.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/17/21. +// + +#include "RealtekRTS525AController.hpp" +#include "Registers.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(RealtekRTS525AController, RealtekRTS5249SeriesController); + +// +// MARK: - Card Power Management +// + +/// +/// Power on the SD card +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_card_power_on()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekRTS525AController::powerOnCard() +{ + using namespace RTSX::Chip::LDO::VCC; + + auto retVal = this->writeChipRegister(rCFG1, CFG1::kTuneMask, CFG1::k3d3V); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the VCC to 3.3V. Error = 0x%x.", retVal); + + return retVal; + } + + return super::powerOnCard(); +} + +/// +/// Switch to the given output voltage +/// +/// @param outputVoltage The new output voltage +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_pci_switch_output_voltage()` defined in `rtsx_psr.c`. +/// +IOReturn RealtekRTS525AController::switchOutputVoltage(OutputVoltage outputVoltage) +{ + using namespace RTSX::Chip; + + ChipRegValuePair pairs[] = + { + { LDO::rCFG2, LDO::CFG2::kD3318Mask, LDO::CFG2::kD33183d3V }, + + { SD::rPADCTL, SD::PADCTL::kUse1d8V, 0 } + }; + + if (outputVoltage == OutputVoltage::k1d8V) + { + pairs[0].value = LDO::CFG2::kD33181d8V; + + pairs[1].value = SD::PADCTL::kUse1d8V; + } + + auto retVal = this->writeChipRegisters(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to inform the hardware the new output voltage level. Error = 0x%x.", retVal); + + return retVal; + } + + return this->setDrivingForOutputVoltage(outputVoltage, false, 100); +} + +// +// MARK: - Hardware Initialization and Configuration +// + +/// +/// Optimize the physical layer +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn RealtekRTS525AController::optimizePhys() +{ + pinfo("Optimizing the physical layer..."); + + using namespace RTSX::Chip; + + pinfo("Enabling the D3 delink mode..."); + + IOReturn retVal = this->writeChipRegister(PM::rCTRL3_52XA, PM::CTRL3::kEnableD3DelinkMode, 0x00); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable D3 delink mode. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("D3 delink mode has been enabled."); + + using namespace RTSX::PHYS; + + const PhysRegValuePair pairs[] = + { + { rFLD0_525A, FLD0_525A::kClkReq20c | FLD0_525A::kRxIdleEnable | FLD0_525A::kBitErrRstn | FLD0_525A::kBerCount | FLD0_525A::kBerTimer | FLD0_525A::kCheckEnable }, + { rANA3, ANA3::kTimerMax | ANA3::kOOBSDebugEnable | ANA3::kCMUDebugEnable }, + { rREV0, REV0::kFilterOut | REV0::kCDRBypassPFD | REV0::kCDRRxIdleBypass } + }; + + if (this->parameters.revision == Revision::kA) + { + return this->writePhysRegisters(pairs); + } + else + { + return this->writePhysRegisters(pairs, 2); + } + + return kIOReturnSuccess; +} + +/// +/// Initialize hardware-specific parameters +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `*_init_params()` defined in each controller file. +/// +IOReturn RealtekRTS525AController::initParameters() +{ + pinfo("Initializing the device-specific parameters..."); + + IOReturn retVal = super::initParameters(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initialize common parameters. Error = 0x%x.", retVal); + + return retVal; + } + + this->parameters.initialTxClockPhase = {25, 29, 11}; + + this->parameters.pm.ltrL1OffSSPowerGate = LTR_L1OFF_SSPWRGATE_5250_DEF; + + this->parameters.pm.ltrL1OffSnoozeSSPowerGate = LTR_L1OFF_SNOOZE_SSPWRGATE_5250_DEF; + + this->parameters.ocp.enable = true; + + this->parameters.ocp.sdGlitch = RTSX::Chip::OCP::GLITCH::kSDValue10M; + + this->parameters.ocp.sdTHD800mA = RTSX::Chip::OCP::PARA2::kSDThdValue800_525A; + + pinfo("Device-specific parameters have been initialized."); + + return kIOReturnSuccess; +} + +// +// MARK: - OOBS Polling +// + +/// +/// Check whether the driver needs to modify the PHY::RCR0 register to enable or disable OOBS polling +/// +/// @return `true` if the controller is not RTS525A nor RTS5260. +/// @note RTS525A and RTS5260 controllers should override this function and return `false`. +/// +bool RealtekRTS525AController::oobsPollingRequiresPhyRCR0Access() +{ + return false; +} + +/// +/// Check whether the hardware supports OOBS polling +/// +/// @return `true` if supported, `false` otherwise. +/// @note By default, this function returns `false`. +/// @note e.g., RTS522A, RTS524A and RTS525A should override this function and return `true`. +/// +bool RealtekRTS525AController::supportsOOBSPolling() +{ + return true; +} + +// +// MARK: - Active State Power Management +// + +/// +/// Set the L1 substates configuration +/// +/// @param active Pass `true` to set the active state +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `rtsx_set_l1off_sub_cfg_d0()` and `set_l1off_cfg_sub_d0()` defined in `rtsx_pcr.c`. +/// @note The base controller provides a default implementation that simply returns `kIOReturnSuccess`. +/// +IOReturn RealtekRTS525AController::setL1OffSubConfigD0(bool active) +{ + UInt8 regVal = 0; + + if (active) + { + // State: Run, Latency: 60us + if (this->parameters.pm.enableASPML11) + { + regVal = this->parameters.pm.ltrL1OffSnoozeSSPowerGate; + } + } + else + { + // State: L1off, Latency: 300us + if (this->parameters.pm.enableASPML12) + { + regVal = this->parameters.pm.ltrL1OffSSPowerGate; + } + } + + if (this->parameters.pm.enableASPML11 || this->parameters.pm.enableASPML12) + { + if (this->parameters.pm.enableLTRL1SSPowerGateCheckCard) + { + if (this->isCardPresent()) + { + regVal &= ~RTSX::Chip::L1SUB::CFG3::kMBIAS2Enable_5250; + } + else + { + regVal |= RTSX::Chip::L1SUB::CFG3::kMBIAS2Enable_5250; + } + } + } + + return this->setL1OffSubConfig(regVal); +} + +// +// MARK: - Hardware Initialization and Configuration +// + +/// +/// [Helper] Get a sequence of registers needed to initialize the hardware +/// +/// @param pairs An array of registers to be populated +/// @return The number of registers in the array. +/// +IOItemCount RealtekRTS525AController::initHardwareExtraGetChipRegValuePairs(ChipRegValuePair (&pairs)[64]) +{ + using namespace RTSX::Chip; + + IOItemCount count = 0; + + auto populate = [&](UInt16 address, UInt8 mask, UInt8 value) -> void + { + pairs[count] = { address, mask, value }; + + count += 1; + }; + + // COMM: Reset the L1SUB config + populate(L1SUB::rCFG3, 0xFF, 0x00); + + // COMM: Turn on the LED + populate(GPIO::rCTL, GPIO::CTL::kLEDMask, GPIO::CTL::kTurnOnLEDValue); + + // COMM: Reset the ASPM state + populate(rAFCTL, 0x3F, 0x00); + + // COMM: Switch the LDO3318 source from DV33 to Card33 + populate(LDO::rPWRSEL, 0x03, 0x00); + populate(LDO::rPWRSEL, 0x03, 0x01); + + // COMM: Set initial LED cycle period + populate(OLT_LED::rCTL, 0x0F, 0x02); + + // COMM: Set reversed socket + populate(rPETXCFG, 0xB0, this->parameters.isSocketReversed ? 0xB0 : 0x80); + + // SPEC: ??? + populate(rVREF, VREF::kEnablePwdSuspnd, VREF::kEnablePwdSuspnd); + + // SPEC: Disable PM wake + populate(PM::rCTRL3_52XA, PM::CTRL3::kEnablePmWake, 0x00); + + // SPEC: Disable D3 delink mode + populate(PM::rCTRL3_52XA, PM::CTRL3::kEnableD3DelinkMode, 0x00); + + // SPEC: ??? + populate(rPFCTL_52XA, 0x30, 0x20); + + // COMM: Request clock + populate(rPETXCFG, PETXCFT::kForceClockRequestDelinkMask, this->parameters.pm.forceClockRequest ? PETXCFT::kForceClockRequestLow : PETXCFT::kForceClockRequestHigh); + + // SPEC: Power off the EFUSE + populate(rPFCTL_52XA, PFCTL_52XA::kEfusePowerMask, PFCTL_52XA::kEfusePowerOff); + + // SPEC: ??? + populate(rCLKCFG3_525A, CLKCFG3_525A::kMemPD, CLKCFG3_525A::kMemPD); + + // SPEC: ??? + populate(rPCLKCTL, PCKLCTL::kModeSelector, PCKLCTL::kModeSelector); + + // SPEC: Revision A + if (this->parameters.revision == Revision::kA) + { + populate(L1SUB::rCFG2, L1SUB::CFG2::kAutoConfig, L1SUB::CFG2::kAutoConfig); + + populate(rRREFCFG, RREFCFG::kVBGSelectorMask, RREFCFG::kVBGSelector1V25); + + populate(LDO::VIO::rCFG, LDO::VIO::CFG::kTuneMask, LDO::VIO::CFG::k1d7V); + + populate(LDO::DV12S::rCFG, LDO::DV12S::CFG::kD12TuneMask, LDO::DV12S::CFG::kD12TuneDefault); + + populate(LDO::AV12S::rCFG, LDO::AV12S::CFG::kTuneMask, LDO::AV12S::CFG::kTuneDefault); + + populate(LDO::VCC::rCFG0, LDO::VCC::CFG0::kLMTVTHMask, LDO::VCC::CFG0::kLMTVTH2A); + + populate(OOBS::rCFG, OOBS::CFG::kAutoKDIS | OOBS::CFG::kValMask, 0x89); + } + + return count; +} diff --git a/RealtekPCIeCardReader/RealtekRTS525AController.hpp b/RealtekPCIeCardReader/RealtekRTS525AController.hpp new file mode 100644 index 0000000..f05bde4 --- /dev/null +++ b/RealtekPCIeCardReader/RealtekRTS525AController.hpp @@ -0,0 +1,126 @@ +// +// RealtekRTS525AController.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/17/21. +// + +#ifndef RealtekRTS525AController_hpp +#define RealtekRTS525AController_hpp + +#include "RealtekRTS5249SeriesController.hpp" + +/// +/// Represents the RTS525A card reader controller +/// +class RealtekRTS525AController: public RealtekRTS5249SeriesController +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareDefaultStructors(RealtekRTS525AController); + + using super = RealtekRTS5249SeriesController; + + // + // MARK: - Card Power Management + // + + /// + /// Power on the SD card + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_card_power_on()` defined in `rtsx_psr.c`. + /// + IOReturn powerOnCard() override; + + /// + /// Switch to the given output voltage + /// + /// @param outputVoltage The new output voltage + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_pci_switch_output_voltage()` defined in `rtsx_psr.c`. + /// + IOReturn switchOutputVoltage(OutputVoltage outputVoltage) override; + + // + // MARK: - OOBS Polling + // + + /// + /// Check whether the driver needs to modify the PHY::RCR0 register to enable or disable OOBS polling + /// + /// @return `true` if the controller is not RTS525A nor RTS5260. + /// @note RTS525A and RTS5260 controllers should override this function and return `false`. + /// + bool oobsPollingRequiresPhyRCR0Access() override; + + /// + /// Check whether the hardware supports OOBS polling + /// + /// @return `true` if supported, `false` otherwise. + /// @note By default, this function returns `false`. + /// @note e.g., RTS522A, RTS524A and RTS525A should override this function and return `true`. + /// + bool supportsOOBSPolling() override; + + // + // MARK: - Active State Power Management + // + + /// + /// Set the L1 substates configuration + /// + /// @param active Pass `true` to set the active state + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `rtsx_set_l1off_sub_cfg_d0()` and `set_l1off_cfg_sub_d0()` defined in `rtsx_pcr.c`. + /// @note The base controller provides a default implementation that simply returns `kIOReturnSuccess`. + /// + IOReturn setL1OffSubConfigD0(bool active) override; + + // + // MARK: - Hardware Initialization and Configuration + // + + /// + /// Optimize the physical layer + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + virtual IOReturn optimizePhys() override; + + /// + /// Initialize hardware-specific parameters + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `*_init_params()` defined in each controller file. + /// + virtual IOReturn initParameters() override; + + // + // MARK: - Hardware Initialization and Configuration + // + + /// + /// [Helper] Get a sequence of registers needed to initialize the hardware + /// + /// @param pairs An array of registers to be populated + /// @return The number of registers in the array. + /// + IOItemCount initHardwareExtraGetChipRegValuePairs(ChipRegValuePair (&pairs)[64]) override; + + // + // MARK: - PCI Device Memory Map + // + + /// + /// Get the 8-bit base address register to map the device memory + /// + /// @return The BAR value 0x10 for all controllers other than RTS525A. + /// @note RTS525A controller must override this function and return 0x14 instead. + /// + UInt8 getDeviceMemoryMapBaseAddressRegister() override { return kIOPCIConfigBaseAddress1; } +}; + +#endif /* RealtekRTS525AController_hpp */ diff --git a/RealtekPCIeCardReader/RealtekSDCommand.hpp b/RealtekPCIeCardReader/RealtekSDCommand.hpp new file mode 100644 index 0000000..596e9ef --- /dev/null +++ b/RealtekPCIeCardReader/RealtekSDCommand.hpp @@ -0,0 +1,362 @@ +// +// RealtekSDCommand.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/30/21. +// + +#ifndef RealtekSDCommand_hpp +#define RealtekSDCommand_hpp + +#include +#include +#include "Registers.hpp" +#include "BitOptions.hpp" +#include "Debug.hpp" + +/// +/// Represents a SD command +/// +struct RealtekSDCommand +{ +private: + /// The command index + UInt32 opcode; + + /// The command argument + UInt32 argument; + + /// The amount of time in ms to detect the busy bit until timed out + UInt32 busyTimeout; + + /// + /// The response buffer + /// + /// @note Type R0: Length = 00; The first byte is the value of `SD_STAT1`. + /// Type R2: Length = 17; The last byte is the value of `SD_STAT1` instead of the CRC7 checksum and the end bit. + /// Others: Length = 06; The last byte is the value of `SD_STAT1` instead of the CRC7 checksum and the end bit. + /// + UInt8 response[17] = {}; + + /// The response type that will be passed to the `SD_CFG2` register + UInt8 responseType; + +public: + // + // MARK: - Constructors + // + + RealtekSDCommand(UInt32 opcode, UInt32 argument, UInt32 busyTimeout, UInt32 responseType) + : opcode(opcode), argument(argument), busyTimeout(busyTimeout), response(), responseType(responseType) {} + + // + // MARK: - Get Command Properties + // + + /// + /// Get the command opcode + /// + /// @return The command opcode. + /// + inline UInt32 getOpcode() const + { + return this->opcode; + } + + /// + /// Get the opcode with the start and the transmission bits set properly + /// + /// @return The opcode with the start bit cleared and the transmission bit set. + /// + inline UInt8 getOpcodeWithStartAndTransmissionBits() const + { + return 0x40 | static_cast(this->opcode); + } + + /// + /// Get the command argument + /// + /// @return The command argument. + /// + inline UInt32 getArgument() const + { + return this->argument; + } + + /// + /// Get the amount of time in ms to detect the busy bit until timed out + /// + /// @param defaultTimeout If the amount of time is not defined (i.e. 0), + /// use the value specified by this parameter instead. + /// + inline UInt32 getBusyTimeout(UInt32 defaultTimeout) const + { + return this->busyTimeout != 0 ? this->busyTimeout : defaultTimeout; + } + + /// + /// Get the storage for the command response + /// + /// @return A non-null pointer to the response buffer. + /// + inline UInt8* getResponseBuffer() + { + return this->response; + } + + /// + /// Reinterpret the command response as the specified type + /// + /// @return A non-null pointer to the response of the specified type. + /// + template + const Response* reinterpretResponseAs() const + { + return reinterpret_cast(this->response); + } + + /// + /// Get the response type + /// + /// @return The reponse type. + /// + inline UInt8 getResponseType() const + { + return this->responseType; + } + + /// + /// Get the response length in bytes + /// + /// @return The number of bytes in the response. + /// + inline IOByteCount getResponseLength() const + { + using namespace RTSX::Chip::SD::CFG2; + + BitOptions type = this->responseType; + + if (type.contains(kResponseLength17)) + { + return 17; + } + else if (type.contains(kResponseLength6)) + { + return 6; + } + else if (type.contains(kResponseLength0)) + { + // Note that we reserve 1 byte for the value of `SD_STAT1` + return 1; + } + else + { + pfatal("Cannot detected the response length. Type = 0x%x.", type.flatten()); + } + } + + /// + /// Check whether the start and the transmission bits in the response are valid or not + /// + /// @return `true` if both bits are valid, `false` otherwise. + /// + inline bool verifyStartAndTransmissionBitsInResponse() const + { + using namespace RTSX::Chip::SD::CFG2; + + // Guard: Check whether the response contains the ST bits + if (this->responseType == kResponseTypeR0) + { + return true; + } + else + { + return (this->response[0] & 0xC0) == 0; + } + } + + /// + /// Check whether the CRC7 checksum is valid + /// + /// @return `true` if the checksum is valid, `false` otherwise. + /// @note Since the last byte in the actual response is the value of the register `SD_STAT1`, + /// this function checks the register value instead of calculating the CRC7 checksum manually. + /// + inline bool verifyCRC7InResponse() const + { + using namespace RTSX::Chip::SD; + + // Guard: Check whether the driver should ignore the CRC7 checksum + if (BitOptions(this->responseType).contains(CFG2::kNoCheckCRC7)) + { + return true; + } + else + { + return (this->response[this->getResponseLength() - 1] & STAT1::kCRC7Error) == 0; + } + } + + /// + /// Print the response in bytes + /// + inline void printResponse() const + { + pinfo("Command Response:"); + + pbufcol(this->response, sizeof(this->response)); + } + + // + // MARK: - Command Factory + // + + /// Enumerates common SD command opcodes + enum Opcode: UInt32 + { + kGoIdleState = 0, + kAllSendCID = 2, + kSendRelativeAddress = 3, + kSwitchFunction = 6, + kSelectCard = 7, + kSendIfCond = 8, + kSendCSD = 9, + kVoltageSwitch = 11, + kStopTransmission = 12, + kSendStatus = 13, + kReadSingleBlock = 17, + kReadMultipleBlocks = 18, + kSendTuningBlock = 19, + kWriteSingleBlock = 24, + kWriteMultipleBlocks = 25, + kAppCommand = 55, + }; + + /// Enumerates common SD application command opcodes + enum AppOpCode: UInt32 + { + kSetBusWidth = 6, + kSDStatus = 13, + kSetEraseCount = 23, + kSendOpCond = 41, + kSendSCR = 51, + }; + + static inline RealtekSDCommand CMD0() + { + return RealtekSDCommand(kGoIdleState, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR0); + } + + static inline RealtekSDCommand CMD2() + { + return RealtekSDCommand(kAllSendCID, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR2); + } + + static inline RealtekSDCommand CMD3() + { + return RealtekSDCommand(kSendRelativeAddress, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR6); + } + + static inline RealtekSDCommand CMD6(UInt32 mode, UInt32 group, UInt32 value) + { + UInt32 argument = mode << 31 | 0x00FFFFFF; + + argument &= ~(0xF << (group * 4)); + + argument |= value << (group * 4); + + return RealtekSDCommand(kSwitchFunction, argument, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD7(UInt32 rca) + { + return RealtekSDCommand(kSelectCard, rca << 16, 0, rca == 0 ? RTSX::Chip::SD::CFG2::kResponseTypeR0 : RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD8(UInt8 vhs, UInt8 checkPattern) + { + return { kSendIfCond, static_cast(vhs << 8 | checkPattern), 0, RTSX::Chip::SD::CFG2::kResponseTypeR7 }; + } + + static inline RealtekSDCommand CMD9(UInt32 rca) + { + return RealtekSDCommand(kSendCSD, rca << 16, 0, RTSX::Chip::SD::CFG2::kResponseTypeR2); + } + + static inline RealtekSDCommand CMD11() + { + return RealtekSDCommand(kVoltageSwitch, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD12() + { + return RealtekSDCommand(kStopTransmission, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD12b() + { + return RealtekSDCommand(kStopTransmission, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1b); + } + + static inline RealtekSDCommand CMD13(UInt32 rca) + { + return RealtekSDCommand(kSendStatus, rca << 16, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD17(UInt32 offset) + { + return RealtekSDCommand(kReadSingleBlock, offset, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD18(UInt32 offset) + { + return RealtekSDCommand(kReadMultipleBlocks, offset, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD19() + { + return RealtekSDCommand(kSendTuningBlock, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD24(UInt32 offset) + { + return RealtekSDCommand(kWriteSingleBlock, offset, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD25(UInt32 offset) + { + return RealtekSDCommand(kWriteMultipleBlocks, offset, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand CMD55(UInt32 rca) + { + return RealtekSDCommand(kAppCommand, rca << 16, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand ACMD6(UInt32 busWidth) + { + return RealtekSDCommand(kSetBusWidth, busWidth, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand ACMD13() + { + return RealtekSDCommand(kSDStatus, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand ACMD23(UInt32 nblocks) + { + return RealtekSDCommand(kSetEraseCount, nblocks, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } + + static inline RealtekSDCommand ACMD41(UInt32 ocr) + { + return RealtekSDCommand(kSendOpCond, ocr, 0, RTSX::Chip::SD::CFG2::kResponseTypeR3); + } + + static inline RealtekSDCommand ACMD51() + { + return RealtekSDCommand(kSendSCR, 0, 0, RTSX::Chip::SD::CFG2::kResponseTypeR1); + } +}; + +#endif /* RealtekSDCommand_hpp */ diff --git a/RealtekPCIeCardReader/RealtekSDRequest.cpp b/RealtekPCIeCardReader/RealtekSDRequest.cpp new file mode 100644 index 0000000..c8e4f2e --- /dev/null +++ b/RealtekPCIeCardReader/RealtekSDRequest.cpp @@ -0,0 +1,111 @@ +// +// RealtekSDRequest.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/30/21. +// + +#include "RealtekSDRequest.hpp" +#include "RealtekSDXCSlot.hpp" + +/// +/// Service the request +/// +/// @param slot A non-null Realtek card slot instance. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function exploits a simple visitor pattern to select the service routine based on the request type. +/// +IOReturn RealtekSDSimpleCommandRequest::service(RealtekSDXCSlot* slot) +{ + return slot->runSDCommand(*this); +} + +/// +/// Service the request +/// +/// @param slot A non-null Realtek card slot instance. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function exploits a simple visitor pattern to select the service routine based on the request type. +/// +IOReturn RealtekSDCommandWithInboundDataTransferRequest::service(RealtekSDXCSlot* slot) +{ + return slot->runSDCommandWithInboundDataTransfer(*this); +} + +/// +/// Service the request +/// +/// @param slot A non-null Realtek card slot instance. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function exploits a simple visitor pattern to select the service routine based on the request type. +/// +IOReturn RealtekSDCommandWithOutboundDataTransferRequest::service(RealtekSDXCSlot* slot) +{ + return slot->runSDCommandWithOutboundDataTransfer(*this); +} + +/// +/// Service the request +/// +/// @param slot A non-null Realtek card slot instance. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function exploits a simple visitor pattern to select the service routine based on the request type. +/// +IOReturn RealtekSDSingleBlockReadRequest::service(RealtekSDXCSlot* slot) +{ + return slot->runSDCommandWithInboundDMATransfer(*this); +} + +/// +/// Service the request +/// +/// @param slot A non-null Realtek card slot instance. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function exploits a simple visitor pattern to select the service routine based on the request type. +/// +IOReturn RealtekSDSingleBlockWriteRequest::service(RealtekSDXCSlot* slot) +{ + return slot->runSDCommandWithOutboundDMATransfer(*this); +} + +/// +/// Service the request +/// +/// @param slot A non-null Realtek card slot instance. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function exploits a simple visitor pattern to select the service routine based on the request type. +/// +IOReturn RealtekSDMultiBlockReadRequest::service(RealtekSDXCSlot* slot) +{ + IOReturn retVal = RealtekSDSingleBlockReadRequest::service(slot); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to service the request that reads multiple blocks. Error = 0x%x.", retVal); + } + + psoftassert(slot->runSDCommand(this->stopCommand) == kIOReturnSuccess, "Failed to send the STOP command."); + + return retVal; +} + +/// +/// Service the request +/// +/// @param slot A non-null Realtek card slot instance. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This function exploits a simple visitor pattern to select the service routine based on the request type. +/// +IOReturn RealtekSDMultiBlockWriteRequest::service(RealtekSDXCSlot* slot) +{ + IOReturn retVal = RealtekSDSingleBlockWriteRequest::service(slot); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to service the request that writes multiple blocks. Error = 0x%x.", retVal); + } + + psoftassert(slot->runSDCommand(this->stopCommand) == kIOReturnSuccess, "Failed to send the STOP command."); + + return retVal; +} diff --git a/RealtekPCIeCardReader/RealtekSDRequest.hpp b/RealtekPCIeCardReader/RealtekSDRequest.hpp new file mode 100644 index 0000000..f54f5e5 --- /dev/null +++ b/RealtekPCIeCardReader/RealtekSDRequest.hpp @@ -0,0 +1,378 @@ +// +// RealtekSDRequest.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/30/21. +// + +#ifndef RealtekSDRequest_hpp +#define RealtekSDRequest_hpp + +#include "RealtekSDCommand.hpp" +#include + +/// Data associated with a SD command +struct RealtekSDData +{ + /// A non-null, prepared DMA command + IODMACommand* data; + + /// The number of blocks to be transfered + UInt64 nblocks; + + /// The size of each block in bytes + UInt64 blockSize; + + /// Create with the given DMA command and data properties + RealtekSDData(IODMACommand* data, UInt64 nblocks, UInt64 blockSize) + : data(data), nblocks(nblocks), blockSize(blockSize) {} + + /// Get the DMA command that describes the data to be transferred + IODMACommand* getIODMACommand() const + { + return this->data; + } + + /// Get the number of blocks to be transferred + UInt16 getNumBlocks() const + { + // e.g. If the storage subsystem requests to read 131072 blocks, + // the host driver must split it into two requests, + // each of which reads 65536 blocks. + // The size of a physical block on a SD card is 512 bytes, + // so the maximum number of bytes in one DMA transfer is 32MB. + psoftassert(this->nblocks <= UINT16_MAX, + "The maximum number of blocks in one transfer is 65536."); + + return static_cast(this->nblocks); + } + + /// Get the size of each block in bytes + UInt16 getBlockSize() const + { + psoftassert(this->blockSize <= UINT16_MAX, + "The maximum number of bytes in one block is 65535."); + + return static_cast(this->blockSize); + } + + /// Get the total amount of data to be transferred + UInt32 getDataLength() const + { + return this->getBlockSize() * this->getNumBlocks(); + } +}; + +/// Forward declaration +class RealtekSDXCSlot; + +/// Specify the interface of an abstract SD request +struct RealtekSDRequest +{ + /// Virtual destructor + virtual ~RealtekSDRequest() = default; + + /// + /// Service the request + /// + /// @param slot A non-null Realtek card slot instance. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function exploits a simple visitor pattern to select the service routine based on the request type. + /// + virtual IOReturn service(RealtekSDXCSlot* slot) = 0; +}; + +/// Represents a simple SD command request that does not involve data transfer (i.e. Use CMD line only) +struct RealtekSDSimpleCommandRequest: RealtekSDRequest +{ + /// A SD command that involves no data transfer + RealtekSDCommand command; + + /// Create a simple SD command request + RealtekSDSimpleCommandRequest(const RealtekSDCommand& command) + : command(command) {} + + /// + /// Service the request + /// + /// @param slot A non-null Realtek card slot instance. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function exploits a simple visitor pattern to select the service routine based on the request type. + /// + IOReturn service(RealtekSDXCSlot* slot) override; +}; + +/// Represents an abstract SD command request that involves a data transfer (i.e. Use CMD + DAT lines) +struct RealtekSDCommandWithDataTransferRequest: RealtekSDSimpleCommandRequest +{ + /// The data associated with the command + RealtekSDData data; + + /// Create a command request that involves a data transfer + RealtekSDCommandWithDataTransferRequest(const RealtekSDCommand& command, const RealtekSDData& data) + : RealtekSDSimpleCommandRequest(command), data(data) {} +}; + +/// Represents a SD command request that involves an inbound data transfer (i.e. Use CMD + DAT lines) +struct RealtekSDCommandWithInboundDataTransferRequest: RealtekSDCommandWithDataTransferRequest +{ + /// Inherit the constructor from the parent class + using RealtekSDCommandWithDataTransferRequest::RealtekSDCommandWithDataTransferRequest; + + /// + /// Service the request + /// + /// @param slot A non-null Realtek card slot instance. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function exploits a simple visitor pattern to select the service routine based on the request type. + /// + IOReturn service(RealtekSDXCSlot* slot) override; +}; + +/// Represents a SD command request that involves an outbound data transfer (i.e. Use CMD + DAT lines) +struct RealtekSDCommandWithOutboundDataTransferRequest: RealtekSDCommandWithDataTransferRequest +{ + /// Inherit the constructor from the parent class + using RealtekSDCommandWithDataTransferRequest::RealtekSDCommandWithDataTransferRequest; + + /// + /// Service the request + /// + /// @param slot A non-null Realtek card slot instance. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function exploits a simple visitor pattern to select the service routine based on the request type. + /// + IOReturn service(RealtekSDXCSlot* slot) override; +}; + +/// Represents an abstract SD command request that involves block-oriented data transfer +struct RealtekSDCommandWithBlockDataTransferRequest: RealtekSDCommandWithDataTransferRequest +{ + /// Inherit the constructor from the parent class + using RealtekSDCommandWithDataTransferRequest::RealtekSDCommandWithDataTransferRequest; +}; + +/// Represents a request that reads a single block from the card (i.e. CMD17) +struct RealtekSDSingleBlockReadRequest: RealtekSDCommandWithBlockDataTransferRequest +{ + /// Inherit the constructor from the parent class + using RealtekSDCommandWithBlockDataTransferRequest::RealtekSDCommandWithBlockDataTransferRequest; + + /// Create a command request that reads a single block from the card + RealtekSDSingleBlockReadRequest(UInt32 offset, IODMACommand* data, UInt64 nblocks, UInt64 blockSize) + : RealtekSDCommandWithBlockDataTransferRequest(RealtekSDCommand::CMD17(offset), RealtekSDData(data, nblocks, blockSize)) {} + + /// + /// Service the request + /// + /// @param slot A non-null Realtek card slot instance. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function exploits a simple visitor pattern to select the service routine based on the request type. + /// + IOReturn service(RealtekSDXCSlot* slot) override; +}; + +/// Represents a request that writes a single block to the card (i.e. CMD24) +struct RealtekSDSingleBlockWriteRequest: RealtekSDCommandWithBlockDataTransferRequest +{ + /// Inherit the constructor from the parent class + using RealtekSDCommandWithBlockDataTransferRequest::RealtekSDCommandWithBlockDataTransferRequest; + + /// Create a command request that writes a single block to the card + RealtekSDSingleBlockWriteRequest(UInt32 offset, IODMACommand* data, UInt64 nblocks, UInt64 blockSize) + : RealtekSDCommandWithBlockDataTransferRequest(RealtekSDCommand::CMD24(offset), RealtekSDData(data, nblocks, blockSize)) {} + + /// + /// Service the request + /// + /// @param slot A non-null Realtek card slot instance. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function exploits a simple visitor pattern to select the service routine based on the request type. + /// + IOReturn service(RealtekSDXCSlot* slot) override; +}; + +/// Represents a request that reads multiple blocks from the card (i.e. CMD18) +/// Note that Realtek's driver does not use CMD23 at this moment, +/// so this is an open ended request that contains a CMD18 and a CMD12. +struct RealtekSDMultiBlockReadRequest: RealtekSDSingleBlockReadRequest +{ + /// Create a command request that reads multiple blocks from the card + RealtekSDMultiBlockReadRequest(UInt32 offset, IODMACommand* data, UInt64 nblocks, UInt64 blockSize) + : RealtekSDSingleBlockReadRequest(RealtekSDCommand::CMD18(offset), RealtekSDData(data, nblocks, blockSize)), + stopCommand(RealtekSDCommand::CMD12()) {} + + /// + /// A CMD12 to terminate the transmission + /// + /// @note If the driver uses a predefined multi-block transfer mode, + /// it still needs to send a CMD12 if there is an error occurred during the transfer. + /// If the driver uses an open ended transfer mode, + /// it must send the final CMD12 to ask the card to stop transmission. + /// As a result, the stop command must be non-null. + /// + RealtekSDCommand stopCommand; + + /// + /// Service the request + /// + /// @param slot A non-null Realtek card slot instance. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function exploits a simple visitor pattern to select the service routine based on the request type. + /// + IOReturn service(RealtekSDXCSlot* slot) override; +}; + +/// Represents a request that writes multiple blocks to the card (i.e. CMD25) +/// Note that Realtek's driver does not use CMD23 at this moment, +/// so this is an open ended request that contains a CMD25 and a CMD12. +/// However, we issue an ACMD23 to set a number of blocks to be pre-erased to improve the write performance. +/// Note that ACMD23 is mandatory for all writable SD cards. +struct RealtekSDMultiBlockWriteRequest: RealtekSDSingleBlockWriteRequest +{ + /// Create a command request that writes multiple blocks to the card + RealtekSDMultiBlockWriteRequest(UInt32 offset, IODMACommand* data, UInt64 nblocks, UInt64 blockSize) + : RealtekSDSingleBlockWriteRequest(RealtekSDCommand::CMD25(offset), RealtekSDData(data, nblocks, blockSize)), + stopCommand(RealtekSDCommand::CMD12b()) {} + + /// + /// A CMD12 to terminate the transmission + /// + /// @note If the driver uses a predefined multi-block transfer mode, + /// it still needs to send a CMD12 if there is an error occurred during the transfer. + /// If the driver uses an open ended transfer mode, + /// it must send the final CMD12 to ask the card to stop transmission. + /// As a result, the stop command must be non-null. + /// + RealtekSDCommand stopCommand; + + /// + /// Service the request + /// + /// @param slot A non-null Realtek card slot instance. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This function exploits a simple visitor pattern to select the service routine based on the request type. + /// + IOReturn service(RealtekSDXCSlot* slot) override; +}; + +namespace RealtekSDRequestFactory +{ + static inline RealtekSDSimpleCommandRequest CMD0() + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD0()); + } + + static inline RealtekSDSimpleCommandRequest CMD2() + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD2()); + } + + static inline RealtekSDSimpleCommandRequest CMD3() + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD3()); + } + + static inline RealtekSDCommandWithInboundDataTransferRequest CMD6(UInt32 mode, UInt32 group, UInt32 value, IODMACommand* data) + { + // TODO: Preferred number of segments to be generated? + // TODO: Start offset of the DMA command? + // TODO: Add more fields to RealtekSDData???? + return RealtekSDCommandWithInboundDataTransferRequest(RealtekSDCommand::CMD6(mode, group, value), RealtekSDData(data, 1, 64)); + } + + static inline RealtekSDSimpleCommandRequest CMD7(UInt32 rca) + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD7(rca)); + } + + static inline RealtekSDSimpleCommandRequest CMD8(UInt8 vhs, UInt8 checkPattern) + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD8(vhs, checkPattern)); + } + + static inline RealtekSDSimpleCommandRequest CMD9(UInt32 rca) + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD9(rca)); + } + + static inline RealtekSDSimpleCommandRequest CMD11() + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD11()); + } + + static inline RealtekSDSimpleCommandRequest CMD13(UInt32 rca) + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD13(rca)); + } + + static inline RealtekSDSingleBlockReadRequest CMD17(UInt32 offset, IODMACommand* data) + { + return RealtekSDSingleBlockReadRequest(offset, data, 1, 512); + } + + static inline RealtekSDMultiBlockReadRequest CMD18(UInt32 offset, IODMACommand* data, UInt64 nblocks) + { + return RealtekSDMultiBlockReadRequest(offset, data, nblocks, 512); + } + + static inline RealtekSDSingleBlockWriteRequest CMD24(UInt32 offset, IODMACommand* data) + { + return RealtekSDSingleBlockWriteRequest(offset, data, 1, 512); + } + + static inline RealtekSDMultiBlockWriteRequest CMD25(UInt32 offset, IODMACommand* data, UInt64 nblocks) + { + return RealtekSDMultiBlockWriteRequest(offset, data, nblocks, 512); + } + + static inline RealtekSDSimpleCommandRequest CMD55(UInt32 rca) + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::CMD55(rca)); + } + + static inline RealtekSDSimpleCommandRequest ACMD6(UInt32 busWidth) + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::ACMD6(busWidth)); + } + + static inline RealtekSDCommandWithInboundDataTransferRequest ACMD13(IODMACommand* data) + { + return RealtekSDCommandWithInboundDataTransferRequest(RealtekSDCommand::ACMD13(), RealtekSDData(data, 1, 64)); + } + + static inline RealtekSDSimpleCommandRequest ACMD23(UInt32 nblocks) + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::ACMD23(nblocks)); + } + + static inline RealtekSDSimpleCommandRequest ACMD41(UInt32 ocr) + { + return RealtekSDSimpleCommandRequest(RealtekSDCommand::ACMD41(ocr)); + } + + static inline RealtekSDCommandWithInboundDataTransferRequest ACMD51(IODMACommand* data) + { + return RealtekSDCommandWithInboundDataTransferRequest(RealtekSDCommand::ACMD51(), RealtekSDData(data, 1, 8)); + } +} + +// Ideally, there should be two kinds of multi block read/write requests +//struct __RealtekSDMultiBlockOpenEndedReadRequest +//{ +// +//}; +// +//struct __RealtekSDMultiBlockPredefinedReadRequest +//{ +// /// +// /// A CMD23 to set a predefined number of blocks to read +// /// +// /// @warning This command can be NULL if the host or the card does not support CMD23. +// /// @note Realtek's driver does not use CMD23 at this moment. +// /// Leave this instance variable for future use. +// /// +// RealtekSDCommand setBlockCountCommand; +//}; + +#endif /* RealtekSDRequest_hpp */ diff --git a/RealtekPCIeCardReader/RealtekSDXCSlot.cpp b/RealtekPCIeCardReader/RealtekSDXCSlot.cpp new file mode 100644 index 0000000..03ec03f --- /dev/null +++ b/RealtekPCIeCardReader/RealtekSDXCSlot.cpp @@ -0,0 +1,2535 @@ +// +// RealtekSDXCSlot.cpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/27/21. +// + +#include "RealtekSDXCSlot.hpp" +#include "IOSDHostDriver.hpp" + +// +// MARK: - Meta Class Definitions +// + +OSDefineMetaClassAndStructors(RealtekSDXCSlot, AppleSDXCSlot); + +// +// MARK: - Constants [Bus Timing Tables] +// + +const ChipRegValuePair RealtekSDXCSlot::kBusTimingTableSDR50[] = +{ + { RTSX::Chip::SD::rCFG1, RTSX::Chip::SD::CFG1::kModeMask | RTSX::Chip::SD::CFG1::kAsyncFIFONotRST, RTSX::Chip::SD::CFG1::kModeSD30 | RTSX::Chip::SD::CFG1::kAsyncFIFONotRST }, + { RTSX::Chip::CLK::rCTL, RTSX::Chip::CLK::CTL::kLowFrequency, RTSX::Chip::CLK::CTL::kLowFrequency }, + { RTSX::Chip::CARD::rCLKSRC, 0xFF, RTSX::Chip::CARD::CLKSRC::kCRCVarClock0 | RTSX::Chip::CARD::CLKSRC::kSD30FixClock | RTSX::Chip::CARD::CLKSRC::kSampleVarClock1 }, + { RTSX::Chip::CLK::rCTL, RTSX::Chip::CLK::CTL::kLowFrequency, 0 }, +}; + +const ChipRegValuePair RealtekSDXCSlot::kBusTimingTableDDR50[] = +{ + { RTSX::Chip::SD::rCFG1, RTSX::Chip::SD::CFG1::kModeMask | RTSX::Chip::SD::CFG1::kAsyncFIFONotRST, RTSX::Chip::SD::CFG1::kModeSDDDR | RTSX::Chip::SD::CFG1::kAsyncFIFONotRST }, + { RTSX::Chip::CLK::rCTL, RTSX::Chip::CLK::CTL::kLowFrequency, RTSX::Chip::CLK::CTL::kLowFrequency }, + { RTSX::Chip::CARD::rCLKSRC, 0xFF, RTSX::Chip::CARD::CLKSRC::kCRCVarClock0 | RTSX::Chip::CARD::CLKSRC::kSD30FixClock | RTSX::Chip::CARD::CLKSRC::kSampleVarClock1 }, + { RTSX::Chip::CLK::rCTL, RTSX::Chip::CLK::CTL::kLowFrequency, 0 }, + { RTSX::Chip::SD::rPPCTL, RTSX::Chip::SD::PPCTL::kDDRVarTxCommandData, RTSX::Chip::SD::PPCTL::kDDRVarTxCommandData }, + { RTSX::Chip::SD::rSPCTL, RTSX::Chip::SD::SPCTL::kDDRVarRxData | RTSX::Chip::SD::SPCTL::kDDRVarRxCommand, RTSX::Chip::SD::SPCTL::kDDRVarRxData | RTSX::Chip::SD::SPCTL::kDDRVarRxCommand }, +}; + +const ChipRegValuePair RealtekSDXCSlot::kBusTimingTableHighSpeed[] = +{ + { RTSX::Chip::SD::rCFG1, RTSX::Chip::SD::CFG1::kModeMask, RTSX::Chip::SD::CFG1::kModeSD20 }, + { RTSX::Chip::CLK::rCTL, RTSX::Chip::CLK::CTL::kLowFrequency, RTSX::Chip::CLK::CTL::kLowFrequency }, + { RTSX::Chip::CARD::rCLKSRC, 0xFF, RTSX::Chip::CARD::CLKSRC::kCRCFixClock | RTSX::Chip::CARD::CLKSRC::kSD30VarClock0 | RTSX::Chip::CARD::CLKSRC::kSampleVarClock1 }, + { RTSX::Chip::CLK::rCTL, RTSX::Chip::CLK::CTL::kLowFrequency, 0 }, + { RTSX::Chip::SD::rPPCTL, RTSX::Chip::SD::PPCTL::kSD20TxSelMask, RTSX::Chip::SD::PPCTL::kSD20Tx14Ahead }, + { RTSX::Chip::SD::rSPCTL, RTSX::Chip::SD::SPCTL::kSD20RxSelMask, RTSX::Chip::SD::SPCTL::kSD20Rx14Delay }, +}; + +const ChipRegValuePair RealtekSDXCSlot::kBusTimingTableDefault[] = +{ + { RTSX::Chip::SD::rCFG1, RTSX::Chip::SD::CFG1::kModeMask, RTSX::Chip::SD::CFG1::kModeSD20 }, + { RTSX::Chip::CLK::rCTL, RTSX::Chip::CLK::CTL::kLowFrequency, RTSX::Chip::CLK::CTL::kLowFrequency }, + { RTSX::Chip::CARD::rCLKSRC, 0xFF, RTSX::Chip::CARD::CLKSRC::kCRCFixClock | RTSX::Chip::CARD::CLKSRC::kSD30VarClock0 | RTSX::Chip::CARD::CLKSRC::kSampleVarClock1 }, + { RTSX::Chip::CLK::rCTL, RTSX::Chip::CLK::CTL::kLowFrequency, 0 }, + { RTSX::Chip::SD::rPPCTL, 0xFF, 0 }, + { RTSX::Chip::SD::rSPCTL, RTSX::Chip::SD::SPCTL::kSD20RxSelMask, RTSX::Chip::SD::SPCTL::kSD20RxPosEdge }, +}; + +// +// MARK: - SD Commander +// + +/// +/// [Shared] Clear the command error +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_clear_error()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::clearError() +{ + using namespace RTSX::Chip::CARD; + + return this->controller->writeChipRegister(rSTOP, STOP::kStopSD | STOP::kClearSDError, STOP::kStopSD | STOP::kClearSDError); +} + +/// +/// [Shared] [Helper] Inform the card reader which SD command to be executed +/// +/// @param command The SD command to be executed +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_cmd_set_sd_cmd()` defined in `rtsx_pci_sdmmc.c`. +/// @warning This function is valid only when there is an active transfer session. +/// i.e. The caller should invoke this function in between `Controller::beginCommandTransfer()` and `Controller::endCommandTransfer()`. +/// +IOReturn RealtekSDXCSlot::setSDCommandOpcodeAndArgument(const RealtekSDCommand& command) +{ + using namespace RTSX::Chip::SD; + + UInt32 argument = command.getArgument(); + + const ChipRegValuePair pairs[] = + { + // Command index + { rCMD0, 0xFF, command.getOpcodeWithStartAndTransmissionBits() }, + + // Command argument in big endian + { rCMD1, 0xFF, static_cast(argument >> 24) }, + { rCMD2, 0xFF, static_cast(argument >> 16) }, + { rCMD3, 0xFF, static_cast(argument >> 8) }, + { rCMD4, 0xFF, static_cast(argument & 0xFF)}, + }; + + return this->controller->enqueueWriteRegisterCommands(SimpleRegValuePairs(pairs)); +} + +/// +/// [Shared] [Helper] Inform the card reader the number of blocks to access +/// +/// @param nblocks The number of blocks +/// @param blockSize The number of bytes in each block +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_cmd_set_data_len()` defined in `rtsx_pci_sdmmc.c`. +/// @warning This function is valid only when there is an active transfer session. +/// i.e. The caller should invoke this function in between `Controller::beginCommandTransfer()` and `Controller::endCommandTransfer()`. +/// +IOReturn RealtekSDXCSlot::setSDCommandDataLength(UInt16 nblocks, UInt16 blockSize) +{ + using namespace RTSX::Chip::SD; + + pinfo("Setting the data length: NumBlocks = %d; Block Size = %d Bytes.", nblocks, blockSize); + + const ChipRegValuePair pairs[] = + { + { rBLOCKCNTL, 0xFF, static_cast(nblocks & 0xFF) }, + { rBLOCKCNTH, 0xFF, static_cast(nblocks >> 8) }, + { rBYTECNTL, 0xFF, static_cast(blockSize & 0xFF) }, + { rBYTECNTH, 0xFF, static_cast(blockSize >> 8) } + }; + + return this->controller->enqueueWriteRegisterCommands(SimpleRegValuePairs(pairs)); +} + +/// +/// [Case 1] Send a SD command and wait for the response +/// +/// @param command The SD command to be sent +/// @param timeout The amount of time in milliseconds to wait for the response until timed out +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_send_cmd_get_rsp()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function checks whether the start and the transmission bits and the CRC7 checksum in the response are valid. +/// Upon a successful return, the response is guaranteed to be valid, but the caller is responsible for verifying the content. +/// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that do not involve a data transfer. +/// +IOReturn RealtekSDXCSlot::runSDCommand(RealtekSDCommand& command, UInt32 timeout) +{ + using namespace RTSX::Chip; + + // Fetch the response type and set up the timeout value + UInt8 responseType = command.getResponseType(); + + if (responseType == SD::CFG2::kResponseTypeR1b) + { + timeout = command.getBusyTimeout(3000); + } + + IOByteCount responseLength = command.getResponseLength(); + + pinfo("SDCMD = %02d; Arg = 0x%08X; Response Length = %llu bytes; Timeout = %d ms.", + command.getOpcode(), command.getArgument(), responseLength, timeout); + + // Start a command transfer session + IOReturn retVal = this->controller->beginCommandTransfer(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate a new command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Set the command opcode and its argument + pinfo("Setting the command opcode and the argument..."); + + retVal = this->setSDCommandOpcodeAndArgument(command); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the command index and argument. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The command opcode and the argument are set."); + + // Set the transfer properties + pinfo("Setting the transfer properties..."); + + const ChipRegValuePair wpairs[] = + { + // Set the response type + { SD::rCFG2, 0xFF, responseType }, + + // Set the data source for the card + // Use the ping pong buffer for SD commands that do not transfer data + { CARD::rDATASRC, CARD::DATASRC::kMask, CARD::DATASRC::kPingPongBuffer }, + + // Transfer an SD command and receive the response + { SD::rTRANSFER, 0xFF, SD::TRANSFER::kTMCmdResp | SD::TRANSFER::kTransferStart }, + }; + + retVal = this->controller->enqueueWriteRegisterCommands(SimpleRegValuePairs(wpairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue a sequence of write operations. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Transfer properties are set."); + + // Once the transfer finishes, we need to check the end and the idle bits in the `SD_TRANSFER` register. + // Note that the Linux driver issues a CHECK REGISTER operation but ignores its return value. + // We will check the register value manually. + retVal = this->controller->enqueueCheckRegisterCommand(SD::rTRANSFER, + SD::TRANSFER::kTransferEnd | SD::TRANSFER::kTransferIdle, + SD::TRANSFER::kTransferEnd | SD::TRANSFER::kTransferIdle); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue a check operation to verify the transfer status. Error = 0x%x.", retVal); + + return retVal; + } + + // Find out where to find the response + pinfo("Setting the location of the command response..."); + + if (responseType == SD::CFG2::kResponseTypeR2) + { + // Read 16 bytes from the ping pong buffer 2 + pinfo("Command Response: Will read 16 bytes from the ping pong buffer 2."); + + retVal = this->controller->enqueueReadRegisterCommands(ContiguousRegValuePairsForReadAccess(PPBUF::rBASE2, 16)); + } + else if (responseType != SD::CFG2::kResponseTypeR0) + { + // Read 5 bytes from the command registers `SD_CMD{0-4}` + pinfo("Command Response: Will read 5 bytes from the command registers."); + + retVal = this->controller->enqueueReadRegisterCommands(ContiguousRegValuePairsForReadAccess(SD::rCMD0, 5)); + } + else + { + // No response + pinfo("Command Response: No response."); + } + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue a sequence of read operations to load the command response. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The location of the command response has been set."); + + // The card reader checks the CRC7 value of the response + // We need to read the value of `SD_STAT1` to ensure that the checksum is valid + retVal = this->controller->enqueueReadRegisterCommand(SD::rSTAT1); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue a read operation to load the command status. Error = 0x%x.", retVal); + + return retVal; + } + + // Finish the command transfer session and wait for the response + retVal = this->controller->endCommandTransfer(timeout); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to terminate the command transfer session. Error = 0x%x.", retVal); + + psoftassert(this->clearError() == kIOReturnSuccess, "Failed to clear the error."); + + return retVal; + } + + pinfo("Verifying the transfer result..."); + + // + // Layout of the host command buffer at this moment: + // + // Offset 00: Value of `SD_TRANSFER` + // Offset 01: The command response + // 0 byte if the response type is R0; + // 6 bytes if the response type is not R2; + // 17 bytes if the response type is R2. + // Offset LB: Value of `SD_STAT1` + // + // Guard: Verify the transfer status + BitOptions transferStatus = this->controller->peekHostBuffer(0); + + if (!transferStatus.contains(SD::TRANSFER::kTransferEnd | SD::TRANSFER::kTransferIdle)) + { + perr("Failed to find the end and the idle bits in the transfer status (0x%02x).", transferStatus.flatten()); + + return kIOReturnInvalid; + } + + pinfo("The transfer status has been verified."); + + // Load the response + retVal = this->controller->readHostBuffer(1, command.getResponseBuffer(), responseLength); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to read the response from the host command buffer. Error = 0x%x.", retVal); + + return retVal; + } + + command.printResponse(); + + if (!command.verifyStartAndTransmissionBitsInResponse()) + { + perr("The start and the transmission bits in the response are invalid."); + + return kIOReturnInvalid; + } + + if (!command.verifyCRC7InResponse()) + { + perr("The CRC7 checksum is invalid."); + + return kIOReturnInvalid; + } + + pinfo("The command response is loaded and verified."); + + return kIOReturnSuccess; +} + +/// +/// [Case 1] Send a SD command and wait for the response +/// +/// @param request The SD command request to process +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_send_cmd_get_rsp()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that do not involve a data transfer. +/// +IOReturn RealtekSDXCSlot::runSDCommand(RealtekSDSimpleCommandRequest& request) +{ + using namespace RTSX::Chip; + + // The host must toggle the clock if the command is CMD11 + if (request.command.getOpcode() != RealtekSDCommand::Opcode::kVoltageSwitch) + { + return this->runSDCommand(request.command); + } + + // Guard: Toggle the clock + IOReturn retVal = this->controller->writeChipRegister(SD::rBUSSTAT, 0xFF, SD::BUSSTAT::kClockToggleEnable); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to toggle the clock. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Run the command + retVal = this->runSDCommand(request.command); + + psoftassert(this->controller->writeChipRegister(SD::rBUSSTAT, SD::BUSSTAT::kClockToggleEnable | SD::BUSSTAT::kClockForceStop, 0) == kIOReturnSuccess, + "Failed to stop the clock."); + + return retVal; +} + +/// +/// [Case 2] Send a SD command and read the data +/// +/// @param command The SD command to be sent +/// @param buffer A buffer to store the response data and is nullable if the given command is one of the tuning command +/// @param length The number of bytes to read (up to 512 bytes) +/// @param timeout The amount of time in milliseonds to wait for the response data until timed out +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_read_data()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a data transfer. +/// +IOReturn RealtekSDXCSlot::runSDCommandAndReadData(const RealtekSDCommand& command, UInt8* buffer, IOByteCount length, UInt32 timeout) +{ + using namespace RTSX::Chip; + + pinfo("SDCMD = %d; Arg = 0x%08X; Data Buffer = 0x%08x%08x; Data Length = %llu bytes; Timeout = %d ms.", + command.getOpcode(), command.getArgument(), KPTR(buffer), length, timeout); + + // Start a command transfer session + IOReturn retVal = this->controller->beginCommandTransfer(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate a new command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Set the command index and the argument + pinfo("Setting the command opcode and the argument..."); + + retVal = this->setSDCommandOpcodeAndArgument(command); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the command index and argument. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The command opcode and the argument are set."); + + // Set the number of data blocks and the size of each block + pinfo("Setting the length of the data blocks associated with the command..."); + + retVal = this->setSDCommandDataLength(1, length); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the command data blocks and length. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("the data length has been set."); + + // Set the transfer properties + pinfo("Setting the transfer properties..."); + + ChipRegValuePair pairs[] = + { + // Ask the card to send the data to the ping pong buffer + // This step is omitted if `command` is a tuning command (i.e., CMD19) + { CARD::rDATASRC, CARD::DATASRC::kMask, CARD::DATASRC::kPingPongBuffer }, + + { SD::rCFG2, 0xFF, SD::CFG2::kCalcCRC7 | SD::CFG2::kCheckCRC7 | SD::CFG2::kCheckCRC16 | SD::CFG2::kNoWaitBusyEnd | SD::CFG2::kResponseLength6 }, + + { SD::rTRANSFER, 0xFF, SD::TRANSFER::kTransferStart } + }; + + // Transfer the commands + if (command.getOpcode() == RealtekSDCommand::Opcode::kSendTuningBlock) + { + pairs[2].value |= SD::TRANSFER::kTMAutoTuning; + + retVal = this->controller->enqueueWriteRegisterCommands(SimpleRegValuePairs(&pairs[1], 2)); + } + else + { + pairs[2].value |= SD::TRANSFER::kTMNormalRead; + + retVal = this->controller->enqueueWriteRegisterCommands(SimpleRegValuePairs(pairs)); + } + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the transfer property. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Transfer properties are set."); + + // Once the transfer finishes, we need to check the end bit in the `SD_TRANSFER` register. + // Note that the Linux driver issues a CHECK REGISTER operation but ignores its return value. + // We will check the register value manually. + retVal = this->controller->enqueueCheckRegisterCommand(SD::rTRANSFER, SD::TRANSFER::kTransferEnd, SD::TRANSFER::kTransferEnd); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue a read operation to load the transfer status. Error = 0x%x.", retVal); + + return retVal; + } + + // Finish the command transfer session and wait for the response + retVal = this->controller->endCommandTransfer(timeout); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to complete the command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Verify the transfer status + pinfo("Verifying the transfer result..."); + + BitOptions transferStatus = this->controller->peekHostBuffer(0); + + if (!transferStatus.contains(SD::TRANSFER::kTransferEnd)) + { + perr("Failed to find the end and the idle bits in the transfer status (0x%02x).", transferStatus.flatten()); + + return kIOReturnInvalid; + } + + // Read the response from the ping pong buffer + if (command.getOpcode() == RealtekSDCommand::Opcode::kSendTuningBlock) + { + pinfo("Tuning Command: No need to read the response from the ping pong buffer."); + + return kIOReturnSuccess; + } + + pinfo("Loading the command response from the ping pong buffer..."); + + retVal = this->controller->readPingPongBuffer(buffer, length); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to load the command response from the ping pong buffer. Error = 0x%x.", retVal); + + return retVal; + } + + pbuf(buffer, length); + + pinfo("The command response has been loaded from the ping pong buffer."); + + return kIOReturnSuccess; +} + +/// +/// [Case 2] Send a SD command along with the data +/// +/// @param command The SD command to be sent +/// @param buffer A non-null buffer that contains the data for the given command +/// @param length The number of bytes to write (up to 512 bytes) +/// @param timeout The amount of time in milliseonds to wait for completion until timed out +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_write_data()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a data transfer. +/// +IOReturn RealtekSDXCSlot::runSDCommandAndWriteData(RealtekSDCommand& command, const UInt8* buffer, IOByteCount length, UInt32 timeout) +{ + using namespace RTSX::Chip; + + pinfo("SDCMD = %d; Arg = 0x%08X; Data Buffer = 0x%08x%08x; Data Length = %llu bytes; Timeout = %d ms.", + command.getOpcode(), command.getArgument(), KPTR(buffer), length, timeout); + + // Send the SD command + pinfo("Sending the SD command..."); + + IOReturn retVal = this->runSDCommand(command); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to send the SD command. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The SD command has been sent."); + + // Write the data to the ping pong buffer + pinfo("Writing the data to the ping pong buffer..."); + + pbufcol(buffer, length); + + retVal = this->controller->writePingPongBuffer(buffer, length); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to write the given data to the ping pong buffer. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Data has been written to the ping pong buffer."); + + // Start a command transfer session + retVal = this->controller->beginCommandTransfer(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate a new command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Set the number of data blocks and the size of each block + pinfo("Setting the length of the data blocks associated with the command..."); + + retVal = this->setSDCommandDataLength(1, length); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the command data blocks and length. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("the data length has been set."); + + // Set the transfer property + pinfo("Setting the transfer properties..."); + + const ChipRegValuePair pairs[] = + { + { SD::rCFG2, 0xFF, SD::CFG2::kCalcCRC7 | SD::CFG2::kCheckCRC7 | SD::CFG2::kCheckCRC16 | SD::CFG2::kNoWaitBusyEnd | SD::CFG2::kResponseLength0 }, + + { SD::rTRANSFER, 0xFF, SD::TRANSFER::kTransferStart | SD::TRANSFER::kTMAutoWrite3 } + }; + + retVal = this->controller->enqueueWriteRegisterCommands(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the transfer property. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Transfer properties are set."); + + // Once the transfer finishes, we need to check the end bit in the `SD_TRANSFER` register. + // Note that the Linux driver issues a CHECK REGISTER operation but ignores its return value. + // We will check the register value manually. + retVal = this->controller->enqueueCheckRegisterCommand(SD::rTRANSFER, SD::TRANSFER::kTransferEnd, SD::TRANSFER::kTransferEnd); + if (retVal != kIOReturnSuccess) + { + perr("Failed to enqueue a read operation to load the transfer status. Error = 0x%x.", retVal); + + return retVal; + } + + // Finish the command transfer session and wait for the response + retVal = this->controller->endCommandTransfer(timeout); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to terminate the command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Verify the transfer status + pinfo("Verifying the transfer result..."); + + BitOptions transferStatus = this->controller->peekHostBuffer(0); + + if (!transferStatus.contains(SD::TRANSFER::kTransferEnd)) + { + perr("Failed to find the end bit in the transfer status (0x%02x).", transferStatus.flatten()); + + return kIOReturnInvalid; + } + + return kIOReturnSuccess; +} + +/// +/// [Case 2] Send a SD command and read the data +/// +/// @param request A data transfer request to service +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces the read portion of `sd_normal_rw()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a data transfer. +/// +IOReturn RealtekSDXCSlot::runSDCommandWithInboundDataTransfer(RealtekSDCommandWithInboundDataTransferRequest& request) +{ + // In this case, the host can transfer up to 512 bytes to the card + // Allocate the buffer on the stack + UInt8 buffer[512]; + + // Guard: Switch the clock divider to 0 if necessary + IOReturn retVal = this->disableInitialModeIfNecessary(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to disable the initial mode. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Fetch the block size from the request + IOByteCount blockSize = request.data.getBlockSize(); + + if (blockSize > arrsize(buffer)) + { + perr("The block size (%llu bytes) is too large. Must not exceed 512 bytes.", blockSize); + + return kIOReturnBadArgument; + } + + // Guard: Send the command and read the data + retVal = this->runSDCommandAndReadData(request.command, buffer, blockSize, 200); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to send the command and read the data. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Switch the clock divider to 128 if necessary + retVal = this->enableInitialModeIfNecessary(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable the initial mode. Error = 0x%x.", retVal); + + return retVal; + } + + // Copy from the intermediate buffer to the DMA command + return request.data.getIODMACommand()->writeBytes(0, buffer, blockSize) == blockSize ? kIOReturnSuccess : kIOReturnDMAError; +} + +/// +/// [Case 2] Send a SD command along with the data +/// +/// @param request A data transfer request to service +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces the write portion of `sd_normal_rw()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a data transfer. +/// +IOReturn RealtekSDXCSlot::runSDCommandWithOutboundDataTransfer(RealtekSDCommandWithOutboundDataTransferRequest& request) +{ + // In this case, the host can transfer up to 512 bytes to the card + // Allocate the buffer on the stack + UInt8 buffer[512]; + + // Guard: Fetch the block size from the request + IOByteCount blockSize = request.data.getBlockSize(); + + if (blockSize > arrsize(buffer)) + { + perr("The block size (%llu bytes) is too large. Must not exceed 512 bytes.", blockSize); + + return kIOReturnBadArgument; + } + + // Guard: Copy from the DMA command to the intermediate buffer + if (request.data.getIODMACommand()->readBytes(0, buffer, blockSize) != blockSize) + { + perr("Failed to copy from the DMA command to an intermediate buffer."); + + return kIOReturnDMAError; + } + + // Guard: Send the command and write the data + return this->runSDCommandAndWriteData(request.command, buffer, blockSize, 200); +} + +/// +/// [Case 3] Send a SD command along with an inbound DMA transfer +/// +/// @param request A block-oriented data transfer request to service +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_read_long_data()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a DMA transfer. +/// +IOReturn RealtekSDXCSlot::runSDCommandWithInboundDMATransfer(RealtekSDCommandWithBlockDataTransferRequest& request) +{ + using namespace RTSX::Chip; + + // Fetch the data length + UInt32 dataLength = request.data.getDataLength(); + + pinfo("SDCMD = %d; Arg = 0x%08X; Data Length = %u bytes.", request.command.getOpcode(), request.command.getArgument(), dataLength); + + // Fetch the response type and set up the SD_CFG2 register value + UInt8 cfg2 = request.command.getResponseType(); + + if (!this->isRunningInUltraHighSpeedMode()) + { + cfg2 |= SD::CFG2::kNoCheckWaitCRCTo; + } + + // Start a command transfer session + IOReturn retVal = this->controller->beginCommandTransfer(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate a new command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Set the command index and the argument + pinfo("Setting the command opcode and the argument..."); + + retVal = this->setSDCommandOpcodeAndArgument(request.command); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the command index and argument. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The command opcode and the argument are set."); + + // Set the number of data blocks and the size of each block + pinfo("Setting the length of the data blocks associated with the command..."); + + retVal = this->setSDCommandDataLength(request.data.getNumBlocks(), request.data.getBlockSize()); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the command data blocks and length. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The data length has been set."); + + // Set the transfer property + pinfo("Setting the transfer properties..."); + + const ChipRegValuePair pairs[] = + { + // Generate an interrupt when the DMA transfer is done + { rIRQSTAT0, IRQSTAT0::kDMADoneInt, IRQSTAT0::kDMADoneInt }, + + // Set up the data length + { DMA::rC3, 0xFF, static_cast(dataLength >> 24) }, + { DMA::rC2, 0xFF, static_cast(dataLength >> 16) }, + { DMA::rC1, 0xFF, static_cast(dataLength >> 8) }, + { DMA::rC0, 0xFF, static_cast(dataLength & 0xFF) }, + + // Set up the direction and the pack size + { DMA::rCTL, DMA::CTL::kEnable | DMA::CTL::kDirectionMask | DMA::CTL::kPackSizeMask, DMA::CTL::kEnable | DMA::CTL::kDirectionFromCard | DMA::CTL::kPackSize512 }, + + // Set up the data transfer + { CARD::rDATASRC, CARD::DATASRC::kMask, CARD::DATASRC::kRingBuffer }, + { SD::rCFG2, 0xFF, cfg2 }, + { SD::rTRANSFER, 0xFF, SD::TRANSFER::kTransferStart | SD::TRANSFER::kTMAutoRead2 }, + }; + + retVal = this->controller->enqueueWriteRegisterCommands(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the transfer property. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Transfer properties are set."); + + // Once the transfer finishes, we need to check the end bit in the `SD_TRANSFER` register. + // Note that the Linux driver issues a CHECK REGISTER operation but ignores its return value. + // We will check the register value manually. +// retVal = this->controller->enqueueCheckRegisterCommand(SD::rTRANSFER, SD::TRANSFER::kTransferEnd, SD::TRANSFER::kTransferEnd); +// +// if (retVal != kIOReturnSuccess) +// { +// perr("Failed to enqueue a read operation to load the transfer status. Error = 0x%x.", retVal); +// +// return retVal; +// } + + // Send the command + retVal = this->controller->endCommandTransfer(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to complete the command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + //this->controller->endCommandTransferNoWait(); + + // Verify the transfer status + //pinfo("Verifying the transfer result..."); + +// BitOptions transferStatus = this->controller->peekHostBuffer(0); +// +// pinfo("[BEFORE] Transfer status is 0x%02x.", transferStatus.flatten()); +// +// if (!transferStatus.contains(SD::TRANSFER::kTransferEnd)) +// { +// pwarning("Failed to find the end and the idle bits in the transfer status (0x%02x).", transferStatus.flatten()); +// +// //return kIOReturnInvalid; +// } + + // Initiate the DMA transfer + pinfo("Initiating the DMA transfer..."); + + retVal = this->controller->performDMARead(request.data.getIODMACommand(), 10000); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to perform the DMA transfer. Error = 0x%x.", retVal); + + psoftassert(this->clearError() == kIOReturnSuccess, "Failed to clear the error."); + + this->controller->dumpChipRegisters({ 0xFDA0, 0xFDB3 }); + + this->controller->dumpChipRegisters({ 0xFD52, 0xFD69 }); + + return retVal; + } + + pinfo("DMA transfer completed successfully."); + + return kIOReturnSuccess; +} + +/// +/// [Case 3] Send a SD command along with an outbound DMA transfer +/// +/// @param request A block-oriented data transfer request to service +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_write_long_data()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a DMA transfer. +/// +IOReturn RealtekSDXCSlot::runSDCommandWithOutboundDMATransfer(RealtekSDCommandWithBlockDataTransferRequest& request) +{ + using namespace RTSX::Chip; + + // Send the SD command + IOReturn retVal = this->runSDCommand(request.command); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to send the SD command. Error = 0x%x.", retVal); + + return retVal; + } + + // Set up the SD_CFG2 register value + UInt8 cfg2 = SD::CFG2::kNoCalcCRC7 | SD::CFG2::kNoCheckCRC7 | SD::CFG2::kCheckCRC16 | SD::CFG2::kNoWaitBusyEnd | SD::CFG2::kResponseLength0; + + if (!this->isRunningInUltraHighSpeedMode()) + { + cfg2 |= SD::CFG2::kNoCheckWaitCRCTo; + } + + // Fetch the data length + UInt32 dataLength = request.data.getDataLength(); + + pinfo("SDCMD = %d; Arg = 0x%08X; Data Length = %u bytes.", request.command.getOpcode(), request.command.getArgument(), dataLength); + + // Start a command transfer session + retVal = this->controller->beginCommandTransfer(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to initiate a new command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + // Set the number of data blocks and the size of each block + pinfo("Setting the length of the data blocks associated with the command..."); + + retVal = this->setSDCommandDataLength(request.data.getNumBlocks(), request.data.getBlockSize()); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the command data blocks and length. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("the data length has been set."); + + // Set the transfer property + pinfo("Setting the transfer properties..."); + + const ChipRegValuePair pairs[] = + { + // Generate an interrupt when the DMA transfer is done + { rIRQSTAT0, IRQSTAT0::kDMADoneInt, IRQSTAT0::kDMADoneInt }, + + // Set up the data length + { DMA::rC3, 0xFF, static_cast(dataLength >> 24) }, + { DMA::rC2, 0xFF, static_cast(dataLength >> 16) }, + { DMA::rC1, 0xFF, static_cast(dataLength >> 8) }, + { DMA::rC0, 0xFF, static_cast(dataLength & 0xFF) }, + + // Set up the direction and the pack size + { DMA::rCTL, DMA::CTL::kEnable | DMA::CTL::kDirectionMask | DMA::CTL::kPackSizeMask, DMA::CTL::kEnable | DMA::CTL::kDirectionToCard | DMA::CTL::kPackSize512 }, + + // Set up the data transfer + { CARD::rDATASRC, CARD::DATASRC::kMask, CARD::DATASRC::kRingBuffer }, + { SD::rCFG2, 0xFF, cfg2 }, + { SD::rTRANSFER, 0xFF, SD::TRANSFER::kTransferStart | SD::TRANSFER::kTMAutoWrite3 }, + }; + + retVal = this->controller->enqueueWriteRegisterCommands(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the transfer property. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("Transfer properties are set."); + + // Once the transfer finishes, we need to check the end bit in the `SD_TRANSFER` register. + // Note that the Linux driver issues a CHECK REGISTER operation but ignores its return value. + // We will check the register value manually. +// retVal = this->controller->enqueueCheckRegisterCommand(SD::rTRANSFER, SD::TRANSFER::kTransferEnd, SD::TRANSFER::kTransferEnd); +// +// if (retVal != kIOReturnSuccess) +// { +// perr("Failed to enqueue a read operation to load the transfer status. Error = 0x%x.", retVal); +// +// return retVal; +// } + + // Send the command + retVal = this->controller->endCommandTransfer(); // Linux uses NoWait(). + + if (retVal != kIOReturnSuccess) + { + perr("Failed to complete the command transfer session. Error = 0x%x.", retVal); + + return retVal; + } + + //this->controller->endCommandTransferNoWait(); + + // Verify the transfer status +// pinfo("Verifying the transfer result..."); +// +// BitOptions transferStatus = this->controller->peekHostBuffer(0); +// +// if (!transferStatus.contains(SD::TRANSFER::kTransferEnd)) +// { +// perr("Failed to find the end and the idle bits in the transfer status (0x%02x).", transferStatus.flatten()); +// +// return kIOReturnInvalid; +// } + + // Initiate the DMA transfer + pinfo("Initiating the DMA transfer..."); + + retVal = this->controller->performDMAWrite(request.data.getIODMACommand(), 10000); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to perform the DMA transfer. Error = 0x%x.", retVal); + + psoftassert(this->clearError() == kIOReturnSuccess, "Failed to clear the error."); + + return retVal; + } + + pinfo("DMA transfer completed successfully."); + + return kIOReturnSuccess; +} + +/// +/// Preprocess the given SD command request +/// +/// @param request A SD command request +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sdmmc_pre_req()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::preprocessRequest(RealtekSDRequest& request) +{ + // TODO: IMP THIS + return 0; +} + +/// +/// Process the given SD command request +/// +/// @param request A SD command request +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_request()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::processRequest(RealtekSDRequest& request) +{ + // Guard: Check whether the card is still present + if (!this->controller->isCardPresent()) + { + perr("The card is not present. Will abort the request."); + + return kIOReturnNoMedia; + } + + // Notify the card reader to enter the worker state + this->controller->enterWorkerState(); + + pinfo("The host driver has sent a SD command request."); + + // Guard: Switch the clock + pinfo("Switching the clock..."); + + IOReturn retVal = this->controller->switchCardClock(this->cardClock, this->sscDepth, this->initialMode, this->doubleClock, this->vpclock); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to switch the clock for the incoming request. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The clock has been switched."); + + // Guard: Select the card + pinfo("Selecting the SD card..."); + + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + { CARD::rSEL, CARD::SEL::kMask, CARD::SEL::kSD }, + { CARD::rSHAREMODE, CARD::SHAREMODE::kMask, CARD::SHAREMODE::k48SD } + }; + + retVal = this->controller->writeChipRegisters(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to select the SD card. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The SD card has been selected."); + + // Guard: Dispatch the request + pinfo("Servicing the request..."); + + retVal = request.service(this); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to service the request. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The request has been serviced."); + + return kIOReturnSuccess; +} + +/// +/// Postprocess the given SD command request +/// +/// @param request A SD command request +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sdmmc_post_req()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::postprocessRequest(RealtekSDRequest& request) +{ + // TODO: IMP THIS + return 0; +} + +// +// MARK: - SD Bus Configurator +// + +/// +/// Check whether the host device is running in the ultra high speed mode +/// +/// @return `true` if the current bus timing mode is one of SDR12/25/50/104 and DDR50 +/// +bool RealtekSDXCSlot::isRunningInUltraHighSpeedMode() +{ + return this->busConfig.busTiming >= IOSDBusConfig::BusTiming::kUHSSDR12 && + this->busConfig.busTiming <= IOSDBusConfig::BusTiming::kUHSDDR50; +} + +/// +/// Set the bus width +/// +/// @param width The target bus width +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_set_bus_width()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. +/// +IOReturn RealtekSDXCSlot::setBusWidth(IOSDBusConfig::BusWidth width) +{ + using namespace RTSX::Chip::SD; + + UInt8 regValue = 0; + + switch (width) + { + case IOSDBusConfig::BusWidth::k1Bit: + { + regValue = CFG1::kBusWidth1Bit; + + pinfo("Will set the bus width to 1 bit."); + + break; + } + + case IOSDBusConfig::BusWidth::k4Bit: + { + regValue = CFG1::kBusWidth4Bit; + + pinfo("Will set the bus width to 4 bits."); + + break; + } + + case IOSDBusConfig::BusWidth::k8Bit: + { + regValue = CFG1::kBusWidth8Bit; + + pinfo("Will set the bus width to 8 bits."); + + break; + } + } + + return this->controller->writeChipRegister(rCFG1, CFG1::kBusWidthMask, regValue); +} + +/// +/// [Helper] Power on the card slot +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_power_on()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. +/// +IOReturn RealtekSDXCSlot::powerOn() +{ + pinfo("Powering on the card slot..."); + + using namespace RTSX::Chip::CARD; + + // Select the SD card and enable the clock + pinfo("Selecting and enabling the SD card..."); + + const ChipRegValuePair pairs[] = + { + { rSEL, SEL::kMask, SEL::kSD }, + { rSHAREMODE, SHAREMODE::kMask, SHAREMODE::k48SD }, + { rCLK, CLK::kEnableSD, CLK::kEnableSD } + }; + + IOReturn retVal = this->controller->transferWriteRegisterCommands(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to select the SD card and enable the clock. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The SD card is selected and enabled."); + + // Enable the card pull control + pinfo("Enabling the pull control for the SD card."); + + retVal = this->controller->enablePullControlForSDCard(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable the pull control for the SD card. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The pull control has been enabled for the SD card."); + + // Power on the SD card + pinfo("Powering on the SD card..."); + + retVal = this->controller->powerOnCard(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to power on the SD card. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The SD card power is now on."); + + // Enable the card output + pinfo("Enabling the card output..."); + + retVal = this->controller->writeChipRegister(rOUTPUT, OUTPUT::kSDMask, OUTPUT::kEnableSDValue); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable the card output. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The card output has been enabled."); + + return kIOReturnSuccess; +} + +/// +/// [Helper] Power off the card slot +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_power_off()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. +/// +IOReturn RealtekSDXCSlot::powerOff() +{ + pinfo("Powering off the card slot..."); + + using namespace RTSX::Chip::CARD; + + // Disable the card clock and the output + pinfo("Disabling the card clock and output..."); + + const ChipRegValuePair pairs[] = + { + { rCLK, CLK::kEnableSD, CLK::kDisable }, + { rOUTPUT, OUTPUT::kSDMask, OUTPUT::kDisableSDValue } + }; + + IOReturn retVal = this->controller->transferWriteRegisterCommands(SimpleRegValuePairs(pairs)); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to disable the clock and the output. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The card clock and output have been disabled."); + + // Power off the card + pinfo("Powering off the card..."); + + retVal = this->controller->powerOffCard(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to power off the SD card. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The card power is now off."); + + // Disable the card pull control + pinfo("Disabling the pull control for the SD card...") + + retVal = this->controller->disablePullControlForSDCard(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to disable the card control. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The pull control has been disabled for the card."); + + return kIOReturnSuccess; +} + +/// +/// Set the power mode +/// +/// @param mode The target power mode +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_set_power_mode()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. +/// +IOReturn RealtekSDXCSlot::setPowerMode(IOSDBusConfig::PowerMode mode) +{ + if (mode == IOSDBusConfig::PowerMode::kPowerOff) + { + return this->powerOff(); + } + else + { + return this->powerOn(); + } +} + +/// +/// Set the bus speed mode +/// +/// @param timing The target bus speed mode +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_set_timing()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. +/// +IOReturn RealtekSDXCSlot::setBusTiming(IOSDBusConfig::BusTiming timing) +{ + switch (timing) + { + case IOSDBusConfig::BusTiming::kUHSSDR104: + case IOSDBusConfig::BusTiming::kUHSSDR50: + { + pinfo("Setting the bus timing for the UHS-I SDR50/SDR104 mode..."); + + return this->controller->transferWriteRegisterCommands(SimpleRegValuePairs(RealtekSDXCSlot::kBusTimingTableSDR50)); + } + + case IOSDBusConfig::BusTiming::kMMCDDR52: + case IOSDBusConfig::BusTiming::kUHSDDR50: + { + pinfo("Setting the bus timing for the UHS-I DDR50 mode..."); + + return this->controller->transferWriteRegisterCommands(SimpleRegValuePairs(RealtekSDXCSlot::kBusTimingTableDDR50)); + } + + case IOSDBusConfig::BusTiming::kMMCHighSpeed: + case IOSDBusConfig::BusTiming::kSDHighSpeed: + { + pinfo("Setting the bus timing for the high speed mode..."); + + return this->controller->transferWriteRegisterCommands(SimpleRegValuePairs(RealtekSDXCSlot::kBusTimingTableHighSpeed)); + } + + default: + { + pinfo("Setting the bus timing for the default speed mode..."); + + return this->controller->transferWriteRegisterCommands(SimpleRegValuePairs(RealtekSDXCSlot::kBusTimingTableDefault)); + } + } +} + +/// +/// Apply the given I/O configuration +/// +/// @param config An I/O config +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sdmmc_set_ios()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::setBusConfig(const IOSDBusConfig& config) +{ + // Guard: Check whether the card is still present + if (!this->controller->isCardPresent()) + { + perr("The card is not present. Will abort the given config."); + + return kIOReturnNoMedia; + } + + pinfo("The host driver requests to change the bus configuration."); + + // Notify the card reader to enter the worker state + this->controller->enterWorkerState(); + + // Set the bus width + pinfo("Setting the bus width..."); + + IOReturn retVal = this->setBusWidth(config.busWidth); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the bus width to %d. Error = 0x%x.", 1 << static_cast(config.busWidth), retVal); + + return retVal; + } + + pinfo("Bus width has been set."); + + // Set the power mode + pinfo("Setting the power mode..."); + + retVal = this->setPowerMode(config.powerMode); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the power mode to %hhu. Error = 0x%x.", config.powerMode, retVal); + + return retVal; + } + + pinfo("The power mode has been set."); + + // Set the bus timing + pinfo("Setting the bus timing mode..."); + + retVal = this->setBusTiming(config.busTiming); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to set the bus timing mode to %hhu. Error = 0x%x.", config.busTiming, retVal); + + return retVal; + } + + pinfo("The bus timing mode has been set."); + + // Set the SSC depth and clock properties + pinfo("Setting the clock..."); + + switch (config.busTiming) + { + case IOSDBusConfig::BusTiming::kUHSSDR104: + case IOSDBusConfig::BusTiming::kUHSSDR50: + { + this->sscDepth = SSCDepth::k2M; + + this->vpclock = true; + + this->doubleClock = false; + + break; + } + + case IOSDBusConfig::BusTiming::kMMCDDR52: + case IOSDBusConfig::BusTiming::kUHSDDR50: + case IOSDBusConfig::BusTiming::kUHSSDR25: + { + this->sscDepth = SSCDepth::k1M; + + this->vpclock = false; + + this->doubleClock = true; + + break; + } + + default: + { + this->sscDepth = SSCDepth::k500K; + + this->vpclock = false; + + this->doubleClock = true; + + break; + } + } + + this->initialMode = config.clock <= MHz2Hz(1); + + this->cardClock = config.clock; + + pinfo("Switching the clock to %d Hz with SSC depth = %d; Initial Mode = %d; Use Double Clock = %d; Use VPCLK = %d...", + this->cardClock, this->sscDepth, this->initialMode, this->doubleClock, this->vpclock); + + retVal = this->controller->switchCardClock(this->cardClock, this->sscDepth, this->initialMode, this->doubleClock, this->vpclock); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to switch the clock."); + + return retVal; + } + + pinfo("The new bus configuration is now active."); + + return kIOReturnSuccess; +} + +/// +/// [Helper] [Phase 1] Wait until the signal voltage becomes stable +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_wait_voltage_stable_1()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is called after the host driver sends a CMD11 to switch to 1.8V. +/// +IOReturn RealtekSDXCSlot::waitVoltageStable1() +{ + using namespace RTSX::Chip; + + // After the host driver sends a CMD11 and receives the response, + // wait for 1 ms so that the card can drive both CMD and DATA lines to low + IOSleep(1); + + // Read the current status of both CMD and DATA lines + UInt8 status; + + IOReturn retVal = this->controller->readChipRegister(SD::rBUSSTAT, status); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to read the command and the data line status. Error = 0x%x.", retVal); + + return retVal; + } + + // Guard: Ensure that both lines are low + if (BitOptions(status).contains(SD::BUSSTAT::kAllLinesStatus)) + { + perr("Current line status is 0x%x. At least one of lines is high.", status); + + return kIOReturnInvalid; + } + + // Stop the SD clock + retVal = this->controller->writeChipRegister(SD::rBUSSTAT, 0xFF, SD::BUSSTAT::kClockForceStop); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to stop the SD clock. Error = 0x%x.", retVal); + + return retVal; + } + + return kIOReturnSuccess; +} + +/// +/// [Helper] [Phase 2] Wait until the signal voltage becomes stable +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_wait_voltage_stable_2()` defined in `rtsx_pci_sdmmc.c`. +/// @note This function is called after the controller switches the signal voltage. +/// +IOReturn RealtekSDXCSlot::waitVoltageStable2() +{ + using namespace RTSX::Chip; + + // Wait until the regulator becomes stable + IOSleep(50); + + // Guard: Enable the SD clock + IOReturn retVal = this->controller->writeChipRegister(SD::rBUSSTAT, 0xFF, SD::BUSSTAT::kClockToggleEnable); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable the SD clock. Error = 0x%x.", retVal); + + return retVal; + } + + // Wait until the card drive both CMD and DATA lines to high + IOSleep(20); + + // Read the current status of both CMD and DATA lines + UInt8 status = 0; + +// retVal = this->controller->readChipRegister(SD::rBUSSTAT, status); +// +// if (retVal != kIOReturnSuccess) +// { +// perr("Failed to read the command and the data line status. Error = 0x%x.", retVal); +// +// return retVal; +// } +// +// // Guard: Ensure that both lines are high +// if (BitOptions(status).contains(SD::BUSSTAT::kAllLinesStatus)) +// { +// return kIOReturnSuccess; +// } + + for (auto attempt = 0; attempt < 200; attempt += 1) + { + pinfo("[%02d] Reading the status of all lines...", attempt); + + retVal = this->controller->readChipRegister(SD::rBUSSTAT, status); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to read the command and the data line status. Error = 0x%x.", retVal); + + IOSleep(20); + + continue; + } + + pinfo("[%02d] Current line status = 0x%02x.", attempt, status); + + // Guard: Ensure that both lines are high + if (BitOptions(status).contains(SD::BUSSTAT::kAllLinesStatus)) + { + return kIOReturnSuccess; + } + + IOSleep(20); + } + + // Stop the card clock + perr("Current line status is 0x%x. At least one of lines is low.", status); + + psoftassert(this->controller->writeChipRegister(SD::rBUSSTAT, SD::BUSSTAT::kClockToggleEnable | SD::BUSSTAT::kClockForceStop, 0) == kIOReturnSuccess, + "Failed to stop and disable the SD clock."); + + psoftassert(this->controller->writeChipRegister(CARD::rCLK, 0xFF, CARD::CLK::kDisable) == kIOReturnSuccess, + "Failed to disable the card clock."); + + return kIOReturnInvalid; +} + +/// +/// [Helper] Switch the signal voltage to 1.8V +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This helper function is called by `RealtekSDXCSlot::switchSignalVoltage()` to simplify error handling. +/// +IOReturn RealtekSDXCSlot::switchSignalVoltage1d8V() +{ + IOReturn retVal = this->waitVoltageStable1(); + + if (retVal != kIOReturnSuccess) + { + return retVal; + } + + retVal = this->controller->switchOutputVoltage(OutputVoltage::k1d8V); + + if (retVal != kIOReturnSuccess) + { + return retVal; + } + + return this->waitVoltageStable2(); +} + +/// +/// [Helper] Switch the signal voltage to 3.3V +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note This helper function is called by `RealtekSDXCSlot::switchSignalVoltage()` to simplify error handling. +/// +IOReturn RealtekSDXCSlot::switchSignalVoltage3d3V() +{ + return this->controller->switchOutputVoltage(OutputVoltage::k3d3V); +} + +/// +/// Switch the signal voltage +/// +/// @param config An I/O config that contains the target signal voltage level +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replace `sdmmc_switch_voltage()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::switchSignalVoltage(const IOSDBusConfig& config) +{ + // Guard: Check whether the card is still present + if (!this->controller->isCardPresent()) + { + perr("The card is not present. Will abort the signal switch request."); + + return kIOReturnNoMedia; + } + + // Notify the card reader to enter the worker state + this->controller->enterWorkerState(); + + using namespace RTSX::Chip::SD; + + IOReturn retVal; + + switch (config.signalVoltage) + { + case IOSDBusConfig::SignalVoltage::k3d3V: + { + pinfo("Will switch the signal voltage level to 3.3V."); + + retVal = this->switchSignalVoltage3d3V(); + + break; + } + + case IOSDBusConfig::SignalVoltage::k1d8V: + { + pinfo("Will switch the signal voltage level to 1.8V."); + + retVal = this->switchSignalVoltage1d8V(); + + break; + } + + case IOSDBusConfig::SignalVoltage::k1d2V: + { + perr("SD Express 1.2V is not supported."); + + return kIOReturnUnsupported; + } + } + + // Verify the result + if (retVal != kIOReturnSuccess) + { + perr("Failed to switch the voltage level. Error = 0x%x.", retVal); + + psoftassert(this->controller->writeChipRegister(rBUSSTAT, BUSSTAT::kClockToggleEnable | BUSSTAT::kClockForceStop, 0) == kIOReturnSuccess, + "Failed to stop the SD clock."); + + return retVal; + } + + pinfo("The signal voltage level has been switched."); + + // Stop toggling the SD clock while in idle + retVal = this->controller->writeChipRegister(rBUSSTAT, BUSSTAT::kClockToggleEnable | BUSSTAT::kClockForceStop, 0); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to stop the SD clock. Error = 0x%x.", retVal); + } + + return retVal; +} + +// +// MARK: - Tuning +// + +/// +/// Change the Rx phase +/// +/// @param samplePoint The sample point value +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces the Rx portion of `sd_change_phase()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::changeRxPhase(UInt8 samplePoint) +{ + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + { CLK::rCTL, CLK::CTL::kChangeClock, CLK::CTL::kChangeClock }, + { SD::rVPRXCTL, SD::VPCTL::kPhaseSelectMask, samplePoint }, + { SD::rVPRXCTL, SD::VPCTL::kPhaseNotReset, 0 }, + { SD::rVPRXCTL, SD::VPCTL::kPhaseNotReset, SD::VPCTL::kPhaseNotReset }, + { CLK::rCTL, CLK::CTL::kChangeClock, 0 }, + { SD::rCFG1, SD::CFG1::kAsyncFIFONotRST, 0 } + }; + + return this->controller->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +/// +/// Change the Tx phase +/// +/// @param samplePoint The sample point value +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces the Tx portion of `sd_change_phase()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::changeTxPhase(UInt8 samplePoint) +{ + using namespace RTSX::Chip; + + const ChipRegValuePair pairs[] = + { + { CLK::rCTL, CLK::CTL::kChangeClock, CLK::CTL::kChangeClock }, + { SD::rVPTXCTL, SD::VPCTL::kPhaseSelectMask, samplePoint }, + { SD::rVPTXCTL, SD::VPCTL::kPhaseNotReset, 0 }, + { SD::rVPTXCTL, SD::VPCTL::kPhaseNotReset, SD::VPCTL::kPhaseNotReset }, + { CLK::rCTL, CLK::CTL::kChangeClock, 0 }, + { SD::rCFG1, SD::CFG1::kAsyncFIFONotRST, 0 } + }; + + return this->controller->writeChipRegisters(SimpleRegValuePairs(pairs)); +} + +/// +/// Get the phase length for the given bit index +/// +/// @param phaseMap The phase map +/// @param sindex The index of the start bit +/// @return The phase length. +/// @note Port: This function replaces `sd_get_phase_len()` and its helper `test_phase_bit()` defined in `rtsx_pci_sdmmc.c`. +/// +UInt32 RealtekSDXCSlot::getPhaseLength(UInt32 phaseMap, UInt32 sindex) +{ + UInt32 index; + + for (index = 0; index < RealtekSDXCSlot::kMaxPhase; index += 1) + { + if (!BitOptions(phaseMap).containsBit((sindex + index) % RealtekSDXCSlot::kMaxPhase)) + { + break; + } + } + + return index; +} + +/// +/// Search for the final phase in the given map +/// +/// @param phaseMap The phase map +/// @return The final sample point value. +/// @note Port: This function replaces `sd_search_final_phase()` defined in `rtsx_pci_sdmmc.c`. +/// +UInt8 RealtekSDXCSlot::searchFinalPhase(UInt32 phaseMap) +{ + UInt8 finalPhase = 0xFF; + + // Guard: The given phase map must be valid + if (phaseMap == 0) + { + pwarning("The given phase map is zero. Will use the final phase 0xFF."); + + return finalPhase; + } + + // Find the index of the start bit that produces the longest phase + UInt32 sindex = 0, length = 0; + + UInt32 fsindex = 0, flength = 0; + + while (sindex < RealtekSDXCSlot::kMaxPhase) + { + length = this->getPhaseLength(phaseMap, sindex); + + if (length > flength) + { + fsindex = sindex; + + flength = length; + } + + sindex += max(1, length); + } + + // Calculate the final phase + finalPhase = (fsindex + flength / 2) % RealtekSDXCSlot::kMaxPhase; + + pinfo("Phase Map = 0x%x; Final Phase = %d; Start Bit Index = %d; Length = %d.", + phaseMap, finalPhase, fsindex, flength); + + return finalPhase; +} + +/// +/// Wait until the data lines are idle +/// +/// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, other values otherwise. +/// @note Port: This function replaces `sd_wait_data_idle()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::waitForIdleDataLine() +{ + using namespace RTSX::Chip::SD; + + UInt8 status; + + IOReturn retVal; + + for (auto attempt = 0; attempt < 100; attempt += 1) + { + retVal = this->controller->readChipRegister(rDATSTATE, status); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to read the data line status. Error = 0x%x.", retVal); + + return retVal; + } + + if (BitOptions(status).contains(DATSTATE::kIdle)) + { + return kIOReturnSuccess; + } + + IODelay(100); + } + + return kIOReturnTimeout; +} + +/// +/// Use the given sample point to tune Rx commands +/// +/// @param samplePoint The sample point +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_tuning_rx_cmd()` defined in `rtsx_pci_sdmmc.c` but removes tuning support for MMC cards. +/// i.e. This function assumes that the command opcode is always 19. +/// +IOReturn RealtekSDXCSlot::tuningRxCommand(UInt8 samplePoint) +{ + using namespace RTSX::Chip::SD; + + // Change the Rx phase + IOReturn retVal = this->changeRxPhase(samplePoint); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to change the Rx phase with the sample point %d. Error = 0x%x.", samplePoint, retVal); + + return retVal; + } + + // Set the timeout + retVal = this->controller->writeChipRegister(rCFG3, CFG3::kEnableResponse80ClockTimeout, CFG3::kEnableResponse80ClockTimeout); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to enable the timeout for the tuning command response. Error = 0x%x.", retVal); + + return retVal; + } + + // Send the tuning command CMD19 + retVal = this->runSDCommandAndReadData(RealtekSDCommand::CMD19(), nullptr, 0x40, 100); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to send the CMD19 and get the response. Error = 0x%x.", retVal); + + // Wait until the data lines become idle + psoftassert(this->waitForIdleDataLine() == kIOReturnSuccess, + "Failed to wait for the data lines to be idle."); + + psoftassert(this->clearError() == kIOReturnSuccess, + "Failed to clear command errors."); + + psoftassert(this->controller->writeChipRegister(rCFG3, CFG3::kEnableResponse80ClockTimeout, 0) == kIOReturnSuccess, + "Failed to disable the timeout for the tuning command response."); + + return retVal; + } + + // Cancel the timeout + retVal = this->controller->writeChipRegister(rCFG3, CFG3::kEnableResponse80ClockTimeout, 0); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to disable the timeout for the tuning command response. Error = 0x%x.", retVal); + + return retVal; + } + + return kIOReturnSuccess; +} + +/// +/// Tune the Rx phase +/// +/// @return The phase map. +/// @note Port: This function replaces `sd_tuning_phase()` defined in `rtsx_pci_sdmmc.c`. +/// +UInt32 RealtekSDXCSlot::tuningRxPhase() +{ + UInt32 phaseMap = 0; + + for (auto index = 0; index < RealtekSDXCSlot::kMaxPhase; index += 1) + { + if (this->tuningRxCommand(index) == kIOReturnSuccess) + { + pinfo("Tuning Rx command with sample point %02d: Success.", index); + + phaseMap |= 1 << index; + } + else + { + pinfo("Tuning Rx command with sample point %02d: Error.", index); + } + } + + return phaseMap; +} + +/// +/// Tune the Rx transfer +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_tuning_rx()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::tuningRx() +{ + // Populate the raw phase maps + UInt32 rawPhaseMaps[RealtekSDXCSlot::kRxTuningCount] = {}; + + for (auto index = 0; index < arrsize(rawPhaseMaps); index += 1) + { + rawPhaseMaps[index] = this->tuningRxPhase(); + + if (rawPhaseMaps[index] == 0) + { + break; + } + } + + // Calculate the phase map + UInt32 phaseMap = 0xFFFFFFFF; + + for (auto index = 0; index < arrsize(rawPhaseMaps); index += 1) + { + pinfo("[%d] Rx Raw Phase Map = 0x%08x.", index, rawPhaseMaps[index]); + + phaseMap &= rawPhaseMaps[index]; + } + + pinfo("Rx Phase Map = 0x%08x.", phaseMap); + + // Verify the phase map + if (phaseMap == 0) + { + perr("Rx Phase Map cannot be zero."); + + return kIOReturnInvalid; + } + + // Calculate the final phase + UInt32 finalPhase = this->searchFinalPhase(phaseMap); + + if (finalPhase == 0xFF) + { + perr("Rx Final Phase cannot be 0xFF."); + + return kIOReturnInvalid; + } + + pinfo("Will change the Rx phase to 0x%08x.", finalPhase); + + return this->changeRxPhase(finalPhase); +} + +/// +/// Execute the tuning algorithm +/// +/// @param config An I/O config that contains the current I/O settings +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sdmmc_execute_tuning()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::executeTuning(const IOSDBusConfig& config) +{ + // Guard: Check whether the card is still present + if (!this->controller->isCardPresent()) + { + perr("The card is not present. Will abort the request."); + + return kIOReturnNoMedia; + } + + // Notify the card reader to enter the worker state + this->controller->enterWorkerState(); + + IOReturn retVal = kIOReturnSuccess; + + switch (config.busTiming) + { + case IOSDBusConfig::BusTiming::kUHSSDR104: + { + pinfo("Will tune Rx and Tx for the UHS SDR104 mode."); + + retVal = this->changeTxPhase(this->controller->getTxClockPhase().sdr104); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to tune the Tx phase. Error = 0x%x.", retVal); + + break; + } + + retVal = this->tuningRx(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to tune the Rx phase. Error = 0x%x.", retVal); + } + + break; + } + + case IOSDBusConfig::BusTiming::kUHSSDR50: + { + pinfo("Will tune Rx and Tx for the UHS SDR50 mode."); + + retVal = this->changeTxPhase(this->controller->getTxClockPhase().sdr50); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to tune the Tx phase. Error = 0x%x.", retVal); + + break; + } + + retVal = this->tuningRx(); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to tune the Rx phase. Error = 0x%x.", retVal); + } + + break; + } + + case IOSDBusConfig::BusTiming::kUHSDDR50: + { + pinfo("Will tune Rx and Tx for the UHS DDR50 mode."); + + retVal = this->changeTxPhase(this->controller->getTxClockPhase().ddr50); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to tune the Tx phase. Error = 0x%x.", retVal); + + break; + } + + retVal = this->changeRxPhase(this->controller->getRxClockPhase().ddr50); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to tune the Rx phase. Error = 0x%x.", retVal); + } + + break; + } + + default: + { + break; + } + } + + return retVal; +} + +// +// MARK: - Card Detection and Write Protection +// + +/// +/// Check whether the card has write protection enabled +/// +/// @param result Set `true` if the card is write protected, `false` otherwise. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sdmmc_get_ro()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::isCardWriteProtected(bool& result) +{ + // Notify the card reader to enter the worker state + this->controller->enterWorkerState(); + + // Guard: Check whether the card is still present + if (!this->controller->isCardPresent()) + { + perr("The card is not present. Will abort the request."); + + return kIOReturnNoMedia; + } + + result = this->controller->isCardWriteProtected(); + + return kIOReturnSuccess; +} + +/// +/// Check whether the card exists +/// +/// @param result Set `true` if the card exists, `false` otherwise. +/// @return `kIOReturnSuccess` always. +/// @note Port: This function replaces `sdmmc_get_cd()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::isCardPresent(bool& result) +{ + // Notify the card reader to enter the worker state + this->controller->enterWorkerState(); + + result = this->controller->isCardPresent(); + + return kIOReturnSuccess; +} + +/// +/// Check whether the command line of the card is busy +/// +/// @param result Set `true` if the card CMD line is high, `false` otherwise. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn RealtekSDXCSlot::isCardCommandLineBusy(bool& result) +{ + // Guard: Check whether the card is still present + if (!this->controller->isCardPresent()) + { + perr("The card is not present. Will abort the request."); + + return kIOReturnNoMedia; + } + + // Notify the card reader to enter the worker state + this->controller->enterWorkerState(); + + using namespace RTSX::Chip::SD; + + UInt8 status; + + IOReturn retVal = this->controller->readChipRegister(rCMDSTATE, status); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to read the command line status. Error = 0x%x.", retVal); + + return retVal; + } + + result = !BitOptions(status).contains(CMDSTATE::kIdle); + + return kIOReturnSuccess; +} + +/// +/// Check whether the data line of the card is busy +/// +/// @param result Set `true` if the card DAT lines are high, `false` otherwise. +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// +IOReturn RealtekSDXCSlot::isCardDataLineBusy(bool& result) +{ + // Guard: Check whether the card is still present + if (!this->controller->isCardPresent()) + { + perr("The card is not present. Will abort the request."); + + return kIOReturnNoMedia; + } + + // Notify the card reader to enter the worker state + this->controller->enterWorkerState(); + + using namespace RTSX::Chip::SD; + + UInt8 status; + + IOReturn retVal = this->controller->readChipRegister(rDATSTATE, status); + + if (retVal != kIOReturnSuccess) + { + perr("Failed to read the data line status. Error = 0x%x.", retVal); + + return retVal; + } + + pinfo("The card data status = 0x%x.", status); + + result = !BitOptions(status).contains(DATSTATE::kIdle); + + return kIOReturnSuccess; +} + +// +// MARK: - Manage Initial Modes +// + +/// +/// Set the clock divider to 128 if `initial mode` is true +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_enable_initial_mode()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::enableInitialModeIfNecessary() +{ + using namespace RTSX::Chip::SD; + + return this->initialMode ? this->controller->writeChipRegister(rCFG1, CFG1::kClockDividerMask, CFG1::kClockDivider128) : kIOReturnSuccess; +} + +/// +/// Set the clock divider to 0 if `initial mode` is true +/// +/// @return `kIOReturnSuccess` on success, other values otherwise. +/// @note Port: This function replaces `sd_disable_initial_mode()` defined in `rtsx_pci_sdmmc.c`. +/// +IOReturn RealtekSDXCSlot::disableInitialModeIfNecessary() +{ + using namespace RTSX::Chip::SD; + + return this->initialMode ? this->controller->writeChipRegister(rCFG1, CFG1::kClockDividerMask, CFG1::kClockDivider0) : kIOReturnSuccess; +} + +// +// MARK: - Fetch Host Capabilities +// + +/// +/// Fetch the host capabilities +/// +void RealtekSDXCSlot::fetchHostCapabilities() +{ + if (this->controller->supportsSDR50()) + { + pinfo("The card reader supports the UHS-I SDR50 mode."); + + this->caps1 |= MMC_CAP_UHS_SDR50; + } + + if (this->controller->supportsSDR104()) + { + pinfo("The card reader supports the UHS-I SDR104 mode."); + + this->caps1 |= MMC_CAP_UHS_SDR104; + } + + if (this->controller->supportsDDR50()) + { + pinfo("The card reader supports the UHS-I DDR50 mode."); + + this->caps1 |= MMC_CAP_UHS_DDR50; + } +} + +// +// MARK: - IOService Implementations +// + +/// +/// Initialize the host device +/// +/// @return `true` on success, `false` otherwise. +/// +bool RealtekSDXCSlot::init(OSDictionary* dictionary) +{ + if (!super::init(dictionary)) + { + return false; + } + + this->maxCurrents = {400, 0, 800}; + + this->clockRange = { KHz2Hz(25), MHz2Hz(208) }; + + this->initialClock = 0; + + this->supportedVoltageRanges = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + + this->caps1 = Capability::k4BitData | + MMC_CAP_SD_HIGHSPEED | + MMC_CAP_MMC_HIGHSPEED | + MMC_CAP_BUS_WIDTH_TEST | + MMC_CAP_UHS_SDR12 | + MMC_CAP_UHS_SDR25; + + this->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE; + + return true; +} + +/// +/// Start the host device +/// +/// @param provider An instance of Realtek PCIe card reader controller +/// @return `true` on success, `false` otherwise. +/// +bool RealtekSDXCSlot::start(IOService* provider) +{ + pinfo("====================================================================="); + pinfo("Starting the host device with the controller at 0x%08x%08x...", KPTR(provider)); + pinfo("====================================================================="); + + // Start the super class + if (!super::start(provider)) + { + perr("Failed to start the super class."); + + return false; + } + + // Get the card reader controller + this->controller = OSDynamicCast(RealtekPCIeCardReaderController, provider); + + if (this->controller == nullptr) + { + perr("The provider is not a valid controller instance."); + + return false; + } + + this->controller->retain(); + + // Fetch the host device capabilities + this->fetchHostCapabilities(); + + // Publish the host driver + pinfo("Publishing the host driver..."); + + IOSDHostDriver* driver = OSTypeAlloc(IOSDHostDriver); + + if (driver == nullptr) + { + perr("Failed to allocate the host driver."); + + return false; + } + + if (!driver->init()) + { + perr("Failed to initialize the host driver."); + + driver->release(); + + return false; + } + + if (!driver->attach(this)) + { + perr("Failed to attach the host driver."); + + driver->release(); + + return false; + } + + if (!driver->start(this)) + { + perr("Failed to start the host driver."); + + driver->detach(this); + + driver->release(); + + return false; + } + + this->driver = driver; + + this->registerService(); + + pinfo("The host driver has been published."); + + pinfo("====================================="); + pinfo("The host device started successfully."); + pinfo("====================================="); + + return true; +} + +/// +/// Stop the host device +/// +/// @param provider An instance of Realtek PCIe card reader controller +/// +void RealtekSDXCSlot::stop(IOService* provider) +{ + pinfo("Stopping the host device..."); + + if (this->driver != nullptr) + { + this->driver->stop(this); + + this->driver->detach(this); + + OSSafeReleaseNULL(this->driver); + } + + OSSafeReleaseNULL(this->controller); + + pinfo("The host device has stopped."); + + super::stop(provider); +} diff --git a/RealtekPCIeCardReader/RealtekSDXCSlot.hpp b/RealtekPCIeCardReader/RealtekSDXCSlot.hpp new file mode 100644 index 0000000..f26d92d --- /dev/null +++ b/RealtekPCIeCardReader/RealtekSDXCSlot.hpp @@ -0,0 +1,527 @@ +// +// RealtekSDXCSlot.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/27/21. +// + +#ifndef RealtekSDXCSlot_hpp +#define RealtekSDXCSlot_hpp + +#include "RealtekPCIeCardReaderController.hpp" +#include "IOSDHostDevice.hpp" +#include "AppleSDXCSlot.hpp" +#include "Utilities.hpp" + +/// +/// Represents a generic Realtek SD (SC/HC/XC) card slot independent of the concrete card reader controller +/// +class RealtekSDXCSlot: public AppleSDXCSlot +{ + // + // MARK: - Constructors & Destructors + // + + OSDeclareDefaultStructors(RealtekSDXCSlot); + + using super = AppleSDXCSlot; + + // + // MARK: - Constants [Bus Timing Tables] + // + + /// Bus Timing Table for SD UHS-I SDR50 and SDR104 mode + static const ChipRegValuePair kBusTimingTableSDR50[]; + + /// Bus Timing Table for SD UHS-I DDR50 mode + static const ChipRegValuePair kBusTimingTableDDR50[]; + + /// Bus Timing Table for SD High Speed mode + static const ChipRegValuePair kBusTimingTableHighSpeed[]; + + /// Bus Timing Table for other modes + static const ChipRegValuePair kBusTimingTableDefault[]; + + // + // MARK: - Constants [Miscellaneous] + // + + /// The maximum phase value + static constexpr UInt32 kMaxPhase = 32; + + /// Tune the Rx phase for three times + static constexpr UInt32 kRxTuningCount = 3; + + // + // MARK: - Private Properties + // + + /// The card reader (provider) + RealtekPCIeCardReaderController* controller; + + /// Card clock in Hz + UInt32 cardClock; + + /// SSC depth value + SSCDepth sscDepth; + + /// `True` if the card is at its initial stage + bool initialMode; + + /// `True` if VPCLOCK is used + bool vpclock; + + /// `True` if the card clock should be doubled + bool doubleClock; + + // + // MARK: - SD Commander + // + +private: + /// + /// [Shared] Clear the command error + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_clear_error()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn clearError(); + + /// + /// [Shared] [Helper] Inform the card reader which SD command to be executed + /// + /// @param command The SD command to be executed + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_cmd_set_sd_cmd()` defined in `rtsx_pci_sdmmc.c`. + /// @warning This function is valid only when there is an active transfer session. + /// i.e. The caller should invoke this function in between `Controller::beginCommandTransfer()` and `Controller::endCommandTransfer()`. + /// + IOReturn setSDCommandOpcodeAndArgument(const RealtekSDCommand& command); + + /// + /// [Shared] [Helper] Inform the card reader the number of blocks to access + /// + /// @param nblocks The number of blocks + /// @param blockSize The number of bytes in each block + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_cmd_set_data_len()` defined in `rtsx_pci_sdmmc.c`. + /// @warning This function is valid only when there is an active transfer session. + /// i.e. The caller should invoke this function in between `Controller::beginCommandTransfer()` and `Controller::endCommandTransfer()`. + /// + IOReturn setSDCommandDataLength(UInt16 nblocks, UInt16 blockSize); + +public: + /// + /// [Case 1] Send a SD command and wait for the response + /// + /// @param command The SD command to be sent + /// @param timeout The amount of time in milliseconds to wait for the response until timed out + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_send_cmd_get_rsp()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function checks whether the start and the transmission bits and the CRC7 checksum in the response are valid. + /// Upon a successful return, the response is guaranteed to be valid, but the caller is responsible for verifying the content. + /// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that do not involve a data transfer. + /// + IOReturn runSDCommand(RealtekSDCommand& command, UInt32 timeout = 100); + + /// + /// [Case 1] Send a SD command and wait for the response + /// + /// @param request The SD command request to process + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_send_cmd_get_rsp()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that do not involve a data transfer. + /// + IOReturn runSDCommand(RealtekSDSimpleCommandRequest& request); + + /// + /// [Case 2] [Helper] Send a SD command and read the data + /// + /// @param command The SD command to be sent + /// @param buffer A buffer to store the response data and is nullable if the given command is one of the tuning command + /// @param length The number of bytes to read (up to 512 bytes) + /// @param timeout The amount of time in milliseonds to wait for the response data until timed out + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_read_data()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a data transfer. + /// + IOReturn runSDCommandAndReadData(const RealtekSDCommand& command, UInt8* buffer, IOByteCount length, UInt32 timeout); + + /// + /// [Case 2] [Helper] Send a SD command along with the data + /// + /// @param command The SD command to be sent + /// @param buffer A non-null buffer that contains the data for the given command + /// @param length The number of bytes to write (up to 512 bytes) + /// @param timeout The amount of time in milliseonds to wait for completion until timed out + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_write_data()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a data transfer. + /// + IOReturn runSDCommandAndWriteData(RealtekSDCommand& command, const UInt8* buffer, IOByteCount length, UInt32 timeout); + + /// + /// [Case 2] Send a SD command and read the data + /// + /// @param request A data transfer request to service + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces the read portion of `sd_normal_rw()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a data transfer. + /// + IOReturn runSDCommandWithInboundDataTransfer(RealtekSDCommandWithInboundDataTransferRequest& request); + + /// + /// [Case 2] Send a SD command along with the data + /// + /// @param request A data transfer request to service + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces the write portion of `sd_normal_rw()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a data transfer. + /// + IOReturn runSDCommandWithOutboundDataTransfer(RealtekSDCommandWithOutboundDataTransferRequest& request); + + /// + /// [Case 3] Send a SD command along with an inbound DMA transfer + /// + /// @param request A block-oriented data transfer request to service + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_read_long_data()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a DMA transfer. + /// + IOReturn runSDCommandWithInboundDMATransfer(RealtekSDCommandWithBlockDataTransferRequest& request); + + IOReturn runSDCommandWithInboundDMATransferV2(RealtekSDCommandWithBlockDataTransferRequest& request); + + /// + /// [Case 3] Send a SD command along with an outbound DMA transfer + /// + /// @param request A block-oriented data transfer request to service + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_write_long_data()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is invoked by `RealtekSDXCSlot::CMD*()` and `RealtekSDXCSlot::ACMD*()` that involve a DMA transfer. + /// + IOReturn runSDCommandWithOutboundDMATransfer(RealtekSDCommandWithBlockDataTransferRequest& request); + + /// + /// Preprocess the given SD command request + /// + /// @param request A SD command request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_pre_req()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn preprocessRequest(RealtekSDRequest& request) override; + + /// + /// Process the given SD command request + /// + /// @param request A SD command request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_request()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn processRequest(RealtekSDRequest& request) override; + + /// + /// Postprocess the given SD command request + /// + /// @param request A SD command request + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_post_req()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn postprocessRequest(RealtekSDRequest& request) override; + + // + // MARK: - SD Bus Configurator + // + +private: + /// + /// Check whether the host device is running in the ultra high speed mode + /// + /// @return `true` if the current bus timing mode is one of SDR12/25/50/104 and DDR50 + /// + bool isRunningInUltraHighSpeedMode(); + + /// + /// Set the bus width + /// + /// @param width The target bus width + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_set_bus_width()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. + /// + IOReturn setBusWidth(IOSDBusConfig::BusWidth width); + + /// + /// [Helper] Power on the card slot + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_power_on()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. + /// + IOReturn powerOn(); + + /// + /// [Helper] Power off the card slot + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_power_off()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. + /// + IOReturn powerOff(); + + /// + /// Set the power mode + /// + /// @param mode The target power mode + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_set_power_mode()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. + /// + IOReturn setPowerMode(IOSDBusConfig::PowerMode mode); + + /// + /// Set the bus speed mode + /// + /// @param timing The target bus speed mode + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_set_timing()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function acts as a service routine of `RealtekSDXCSlot::setIOConfig()`. + /// + IOReturn setBusTiming(IOSDBusConfig::BusTiming timing); + + /// + /// Apply the given I/O configuration + /// + /// @param config An I/O config + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_set_ios()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn setBusConfig(const IOSDBusConfig& config) override; + + /// + /// [Helper] [Phase 1] Wait until the signal voltage becomes stable + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_wait_voltage_stable_1()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is called after the host driver sends a CMD11 to switch to 1.8V. + /// + IOReturn waitVoltageStable1(); + + /// + /// [Helper] [Phase 2] Wait until the signal voltage becomes stable + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_wait_voltage_stable_2()` defined in `rtsx_pci_sdmmc.c`. + /// @note This function is called after the controller switches the signal voltage. + /// + IOReturn waitVoltageStable2(); + + /// + /// [Helper] Switch the signal voltage to 1.8V + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This helper function is called by `RealtekSDXCSlot::switchSignalVoltage()` to simplify error handling. + /// + IOReturn switchSignalVoltage1d8V(); + + /// + /// [Helper] Switch the signal voltage to 3.3V + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note This helper function is called by `RealtekSDXCSlot::switchSignalVoltage()` to simplify error handling. + /// + IOReturn switchSignalVoltage3d3V(); + +public: + /// + /// Switch the signal voltage + /// + /// @param config An I/O config that contains the target signal voltage level + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replace `sdmmc_switch_voltage()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn switchSignalVoltage(const IOSDBusConfig& config) override; + + // + // MARK: - Tuning + // + +private: + /// + /// Change the Rx phase + /// + /// @param samplePoint The sample point value + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces the Rx portion of `sd_change_phase()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn changeRxPhase(UInt8 samplePoint); + + /// + /// Change the Tx phase + /// + /// @param samplePoint The sample point value + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces the Tx portion of `sd_change_phase()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn changeTxPhase(UInt8 samplePoint); + + /// + /// Get the phase length for the given bit index + /// + /// @param phaseMap The phase map + /// @param sindex The index of the start bit + /// @return The phase length. + /// @note Port: This function replaces `sd_get_phase_len()` and its helper `test_phase_bit()` defined in `rtsx_pci_sdmmc.c`. + /// + UInt32 getPhaseLength(UInt32 phaseMap, UInt32 sindex); + + /// + /// Search for the final phase in the given map + /// + /// @param phaseMap The phase map + /// @return The final sample point value. + /// @note Port: This function replaces `sd_search_final_phase()` defined in `rtsx_pci_sdmmc.c`. + /// + UInt8 searchFinalPhase(UInt32 phaseMap); + + /// + /// Wait until the data lines are idle + /// + /// @return `kIOReturnSuccess` on success, `kIOReturnTimeout` if timed out, other values otherwise. + /// @note Port: This function replaces `sd_wait_data_idle()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn waitForIdleDataLine(); + + /// + /// Use the given sample point to tune Rx commands + /// + /// @param samplePoint The sample point + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_tuning_rx_cmd()` defined in `rtsx_pci_sdmmc.c` but removes tuning support for MMC cards. + /// i.e. This function assumes that the command opcode is always 19. + /// + IOReturn tuningRxCommand(UInt8 samplePoint); + + /// + /// Tune the Rx phase + /// + /// @return The phase map. + /// @note Port: This function replaces `sd_tuning_phase()` defined in `rtsx_pci_sdmmc.c`. + /// + UInt32 tuningRxPhase(); + + /// + /// Tune the Rx transfer + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_tuning_rx()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn tuningRx(); + +public: + /// + /// Execute the tuning algorithm + /// + /// @param config An I/O config that contains the current I/O settings + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_execute_tuning()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn executeTuning(const IOSDBusConfig& config) override; + + // + // MARK: - Card Detection and Write Protection + // + + /// + /// Check whether the card has write protection enabled + /// + /// @param result Set `true` if the card is write protected, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sdmmc_get_ro()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn isCardWriteProtected(bool& result) override; + + /// + /// Check whether the card exists + /// + /// @param result Set `true` if the card exists, `false` otherwise. + /// @return `kIOReturnSuccess` always. + /// @note Port: This function replaces `sdmmc_get_cd()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn isCardPresent(bool& result) override; + + /// + /// Check whether the command line of the card is busy + /// + /// @param result Set `true` if the card CMD line is high, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn isCardCommandLineBusy(bool& result) override; + + /// + /// Check whether the data line of the card is busy + /// + /// @param result Set `true` if the card DAT lines are high, `false` otherwise. + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// + IOReturn isCardDataLineBusy(bool& result) override; + + // + // MARK: - Manage Initial Modes + // + +private: + /// + /// Set the clock divider to 128 if `initial mode` is true + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_enable_initial_mode()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn enableInitialModeIfNecessary(); + + /// + /// Set the clock divider to 0 if `initial mode` is true + /// + /// @return `kIOReturnSuccess` on success, other values otherwise. + /// @note Port: This function replaces `sd_disable_initial_mode()` defined in `rtsx_pci_sdmmc.c`. + /// + IOReturn disableInitialModeIfNecessary(); + + // + // MARK: - Fetch Host Capabilities + // + + /// + /// Fetch the host capabilities + /// + void fetchHostCapabilities(); + + // + // MARK: - IOService Implementations + // + +public: + /// + /// Initialize the host device + /// + /// @return `true` on success, `false` otherwise. + /// + bool init(OSDictionary* dictionary = nullptr) override; + + /// + /// Start the host device + /// + /// @param provider An instance of Realtek PCIe card reader controller + /// @return `true` on success, `false` otherwise. + /// + bool start(IOService* provider) override; + + /// + /// Stop the host device + /// + /// @param provider An instance of Realtek PCIe card reader controller + /// + void stop(IOService* provider) override; +}; + +#endif /* RealtekSDXCSlot_hpp */ diff --git a/RealtekPCIeCardReader/Registers.hpp b/RealtekPCIeCardReader/Registers.hpp new file mode 100644 index 0000000..228f667 --- /dev/null +++ b/RealtekPCIeCardReader/Registers.hpp @@ -0,0 +1,1820 @@ +// +// Registers.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/17/21. +// + +#ifndef Registers_hpp +#define Registers_hpp + +#include +#include "Utilities.hpp" + +// +// Overview +// +// Broadly, registers are classified into three categories, and controllers provide specical routines to access them. +// 1) MMIO: Registers have a 32-bit address and can be accessed via `readRegister8/16/32` and `writeRegister8/16/32`. +// 2) Chip: Registers have a 16-bit address and a 8-bit value. +// Can be accessed via `readChipRegister` and `writeChipRegister`. +// 3) PHY: Registers have a 8-bit address and a 16-bit value. +// Can be accessed via `readPhysRegister` and `writePhysRegister`. +// Categories are reflected by the namespace. +// Some registers may have special masks and flags, and these values are defined in a nested namespace named after the register. +// + +// +// MARK: - 0. Macros & Data Structures +// + +#define RTSXDeclareMMIORegister(name, address) \ + static constexpr UInt32 name = address; + +#define RTSXDeclareChipRegister(name, address) \ + static constexpr UInt16 name = address; + +#define RTSXDeclarePhysRegister(name, address) \ + static constexpr UInt8 name = address; + +#define RTSXDeclareMMIORegisterValue32(name, value) \ + static constexpr UInt32 name = value; + +#define RTSXDeclareChipRegisterValue(name, value) \ + static constexpr UInt8 name = value; + +#define RTSXDeclarePhysRegisterValue(name, value) \ + static constexpr UInt16 name = value; + + +/// Represents a pair of register address and its value +struct ChipRegValuePair +{ + /// The register address + UInt16 address; + + /// The register value mask + UInt8 mask; + + /// The register value + UInt8 value; + + /// Create a pair + ChipRegValuePair(UInt16 address, UInt8 mask, UInt8 value) : + address(address), mask(mask), value(value) {} + + /// Create a pair with mask set to 0xFF + ChipRegValuePair(UInt16 address, UInt8 value) : + ChipRegValuePair(address, 0xFF, value) {} + + /// Create an empty pair + ChipRegValuePair() : + ChipRegValuePair(0, 0, 0) {} +}; + +/// Interface of a sequence of pairs of register address and its value +struct ChipRegValuePairs +{ + /// Virtual destructor + virtual ~ChipRegValuePairs() = default; + + /// Retrieve the i-th register value pair + virtual struct ChipRegValuePair get(size_t index) const = 0; + + /// Retrieve the total number of pairs + virtual IOItemCount size() const = 0; +}; + +/// +/// Composed of a simple array of pairs of register address and value +/// +/// @note Commonly used to access a small number of registers. +/// +struct SimpleRegValuePairs: ChipRegValuePairs +{ +private: + /// An array of pairs + const ChipRegValuePair* pairs; + + /// The number of pair + const IOItemCount count; + +public: + /// Create a simple sequence of pairs + SimpleRegValuePairs(const ChipRegValuePair* pairs, IOItemCount count) : + pairs(pairs), count(count) {} + + /// Create a simple sequence of pairs + template + SimpleRegValuePairs(const ChipRegValuePair (&pairs)[N]) : + pairs(pairs), count(N) {} + + /// Retrieve the i-th register value pair + struct ChipRegValuePair get(size_t index) const override + { + return this->pairs[index]; + } + + /// Retrieve the total number of pairs + IOItemCount size() const override + { + return this->count; + } +}; + +/// +/// Composed of the base register address to generate a forward sequence of pairs for read access dynamically +/// +/// @note Convenient to read a contiguous sequence of registers. +/// +struct ContiguousRegValuePairsForReadAccess: ChipRegValuePairs +{ +private: + /// The base register address + const UInt16 baseRegister; + + /// The number of pairs to generate + const IOItemCount count; + +public: + /// + /// Create a contiguous sequence of pairs for read access + /// + /// @param baseRegister The base register address + /// @param count The number of registers + /// + ContiguousRegValuePairsForReadAccess(UInt16 baseRegister, IOItemCount count) : + baseRegister(baseRegister), count(count) {} + + /// Retrieve the i-th register value pair + struct ChipRegValuePair get(size_t index) const override + { + return ChipRegValuePair(static_cast(this->baseRegister + index), 0, 0); + } + + /// Retrieve the total number of pairs + IOItemCount size() const override + { + return this->count; + } +}; + +/// +/// Composed of the base register address to generate a forward sequence of pairs for write access dynamically +/// +/// @note Convenient to read a contiguous sequence of registers. +/// +struct ContiguousRegValuePairsForWriteAccess: ChipRegValuePairs +{ +private: + /// The base register address + const UInt16 baseRegister; + + /// The register value mask + const UInt8 mask; + + /// The number of pairs to generate + const IOItemCount count; + + /// The register values + const UInt8* values; + +public: + /// + /// Create a contiguous sequence of pairs for write access + /// + /// @param baseRegister The base register address + /// @param count The number of registers + /// @param values An array of register values + /// @param mask The regiter value mask, 0xFF by default + /// + ContiguousRegValuePairsForWriteAccess(UInt16 baseRegister, IOItemCount count, const UInt8* values, UInt8 mask = 0xFF) : + baseRegister(baseRegister), mask(mask), count(count), values(values) {} + + /// Retrieve the i-th register value pair + struct ChipRegValuePair get(size_t index) const override + { + return ChipRegValuePair(static_cast(this->baseRegister + index), this->mask, this->values[index]); + } + + /// Retrieve the total number of pairs + IOItemCount size() const override + { + return this->count; + } +}; + +/// Represents a pair of physical register and its value +struct PhysRegValuePair +{ + UInt8 address; + + UInt8 reserved; + + UInt16 value; + + PhysRegValuePair(UInt8 address, UInt16 value) + : address(address), reserved(0), value(value) {} +}; + +/// Represents a pair of physical register and its mask and value +struct PhysRegMaskValuePair +{ + UInt8 reserved[3]; + + UInt8 address; + + UInt16 mask; + + UInt16 value; + + PhysRegMaskValuePair(UInt8 address, UInt16 mask, UInt16 value) + : reserved(), address(address), mask(mask), value(value) {} +}; + +// +// MARK: - 1. MMIO Registers +// + +/// MMIO registers that controls the host command buffer +namespace RTSX::MMIO +{ + /// Address of the register that receives the host command buffer address + RTSXDeclareMMIORegister(rHCBAR, 0x00); + + /// Special values and flags for the HCBAR register + namespace HCBAR + { + /// The maximum number of commands in the queue + static constexpr IOItemCount kMaxNumCmds = 256; + } + + /// Address of the register that initiates and terminates the execution of host commands + RTSXDeclareMMIORegister(rHCBCTLR, 0x04); + + /// Special values and flags for the HCBCTLR register + namespace HCBCTLR + { + /// Bit 31 is set to start executing commands + RTSXDeclareMMIORegisterValue32(kStartCommand, 1 << 31); + + /// Bit 30 is set to turn on the automatic hardware response + RTSXDeclareMMIORegisterValue32(kAutoResponse, 1 << 30); + + /// Bit 28 is set to terminate executing commands + RTSXDeclareMMIORegisterValue32(kStopCommand, 1 << 28); + + /// Register value to start executing commands + static inline UInt32 RegValueForStartCommand(IOItemCount count) + { + return kStartCommand | kAutoResponse | ((count * sizeof(UInt32)) & 0x00FFFFFF); + } + } +} + +/// MMIO registers that controls the host data buffer +namespace RTSX::MMIO +{ + /// Address of the register that receives the host data buffer address + RTSXDeclareMMIORegister(rHDBAR, 0x08); + + /// Special values and flags for the HDBAR register + namespace HDBAR + { + /// The maximum number of data elements in the queue + static constexpr IOItemCount kMaxNumElements = 384; + + /// The following values are appended to each scatter/gather list entry in the host data buffer + static constexpr UInt8 kSGOptionInt = 0x04; + static constexpr UInt8 kSGOptionEnd = 0x02; + static constexpr UInt8 kSGOptionValid = 0x01; + static constexpr UInt8 kSGOptionNoOp = 0x00; + static constexpr UInt8 kSGOptionTransferData = 0x02 << 4; + static constexpr UInt8 kSGOptionLinkDescriptor = 0x03 << 4; + } + + /// Address of the register that initiates and terminates the transfer of host data buffer + RTSXDeclareMMIORegister(rHDBCTLR, 0x0C); + + /// Special values and flags for the HDBCTLR register + namespace HDBCTLR + { + /// Bit 31 is set to start a DMA transfer + RTSXDeclareMMIORegisterValue32(kStartDMA, 1 << 31); + + /// Bit 29 is set to indicate a read operation + RTSXDeclareMMIORegisterValue32(kDMARead, 1 << 29); + + /// Bit 28 is set to stop a DMA transfer + RTSXDeclareMMIORegisterValue32(kStopDMA, 1 << 28); + + /// Bit 26-27 is set to 0b10 to turn on ADMA mode + RTSXDeclareMMIORegisterValue32(kUseADMA, 2 << 26); + } +} + +/// MMIO registers that can be programmed to access chip registers +namespace RTSX::MMIO +{ + /// Address of the register used to access chip registers + RTSXDeclareMMIORegister(rHAIMR, 0x10); + + /// Special values and flags for the HAIMR register + namespace HAIMR + { + /// The maximum number of attempts + static constexpr UInt32 kMaxAttempts = 1024; + + /// Bit 31 is set when the chip is busy + RTSXDeclareMMIORegisterValue32(kStatusBusy, 1 << 31) + + /// Bit 30 is set to perform a write operation + RTSXDeclareMMIORegisterValue32(kWriteOperation, 1 << 30) + + /// Register value to start a read operation + static inline UInt32 RegValueForReadOperation(UInt16 address) + { + // Set the busy bit and clear the write operation bit + return kStatusBusy | static_cast(address & 0x3FFF) << 16; + } + + /// Register value to start a write operation + static inline UInt32 RegValueForWriteOperation(UInt16 address, UInt8 mask, UInt8 value) + { + // Set the busy bit and the write operation bit + return kStatusBusy | kWriteOperation | static_cast(address & 0x3FFF) << 16 | static_cast(mask) << 8 | value; + } + + /// Check whether the device is busy by examining its register value + static inline bool IsBusy(UInt32 value) + { + return (value & kStatusBusy) == kStatusBusy; + } + } +} + +/// MMIO registers that reports which hardware interrupts are pending +namespace RTSX::MMIO +{ + /// Address of the register used to retrieve all pending interrupts + RTSXDeclareMMIORegister(rBIPR, 0x14); + + /// Special values and flags for the BIER register + namespace BIPR + { + /// Bit 31 is set when command transfer is done + RTSXDeclareMMIORegisterValue32(kCommandTransferDone, 1 << 31); + + /// Bit 30 is set when data transfer is done + RTSXDeclareMMIORegisterValue32(kDataTransferDone, 1 << 30); + + /// Bit 29 is set when transfer has succeeded + RTSXDeclareMMIORegisterValue32(kTransferSucceeded, 1 << 29); + + /// Bit 28 is set when transfer has failed + RTSXDeclareMMIORegisterValue32(kTransferFailed, 1 << 28); + + /// Bit 27 is set when a XD interrupt is pending + RTSXDeclareMMIORegisterValue32(kXD, 1 << 27); + + /// Bit 26 is set when a MS interrupt is pending + RTSXDeclareMMIORegisterValue32(kMS, 1 << 26); + + /// Bit 25 is set when a SD interrupt is pending + RTSXDeclareMMIORegisterValue32(kSD, 1 << 25); + + /// Bit 24 is set when a GPIO0 interrupt is pending + RTSXDeclareMMIORegisterValue32(kGPIO0, 1 << 24); + + /// Bit 23 is set when MS overcurrent has occurred + RTSXDeclareMMIORegisterValue32(kMSOvercurrentOccurred, 1 << 23); + + /// Bit 22 is set when SD overcurrent has occurred + RTSXDeclareMMIORegisterValue32(kSDOvercurrentOccurred, 1 << 22); + + /// Bit 19 is set when SD is write protected + RTSXDeclareMMIORegisterValue32(kSDWriteProtected, 1 << 19); + + /// Bit 18 is set when XD card exists + RTSXDeclareMMIORegisterValue32(kXDExists, 1 << 18); + + /// Bit 17 is set when MS card exists + RTSXDeclareMMIORegisterValue32(kMSExists, 1 << 17); + + /// Bit 16 is set when SD card exists + RTSXDeclareMMIORegisterValue32(kSDExists, 1 << 16); + } +} + +/// MMIO registers that controls which hardware interrupts are enabled +namespace RTSX::MMIO +{ + /// Address of the register used to enable certain interrupts + RTSXDeclareMMIORegister(rBIER, 0x18); + + /// Special values and flags for the BIER register + namespace BIER + { + /// Bit 31 is set to enable the interrupt when command transfer is done + RTSXDeclareMMIORegisterValue32(kEnableCommandTransferDone, 1 << 31); + + /// Bit 30 is set to enable the interrupt when data transfer is done + RTSXDeclareMMIORegisterValue32(kEnableDataTransferDone, 1 << 30); + + /// Bit 29 is set to enable the interrupt when transfer has succeeded + RTSXDeclareMMIORegisterValue32(kEnableTransferSuccess, 1 << 29); + + /// Bit 28 is set to enable the interrupt when transfer has failed + RTSXDeclareMMIORegisterValue32(kEnableTransferFailure, 1 << 28); + + /// Bit 27 is set to enable XD interrupt + RTSXDeclareMMIORegisterValue32(kEnableXD, 1 << 27); + + /// Bit 26 is set to enable MS interrupt + RTSXDeclareMMIORegisterValue32(kEnableMS, 1 << 26); + + /// Bit 25 is set to enable SD interrupt + RTSXDeclareMMIORegisterValue32(kEnableSD, 1 << 25); + + /// Bit 24 is set to enable GPIO0 interrupt + RTSXDeclareMMIORegisterValue32(kEnableGPIO0, 1 << 24); + + /// Bit 23 is set to enable MS overcurrent interrupt + RTSXDeclareMMIORegisterValue32(kEnableMSOvercurrent, 1 << 23); + + /// Bit 22 is set to enable SD overcurrent interrupt + RTSXDeclareMMIORegisterValue32(kEnableSDOvercurrent, 1 << 22); + + /// Clear all bits to disable all interrupts + RTSXDeclareMMIORegisterValue32(kDisableAll, 0); + } +} + +// +// MARK: - 2. Chip Registers +// + +/// Chip registers that controls the power of SSC and OCP +namespace RTSX::Chip +{ + RTSXDeclareChipRegister(rFPDCTL, 0xFC00); + + namespace FPDCTL + { + /// Bit 0 is set to power down SSC + RTSXDeclareChipRegisterValue(kSSCPowerMask, 0x01); + RTSXDeclareChipRegisterValue(kSSCPowerUpValue, 0x00); + RTSXDeclareChipRegisterValue(kSSCPowerDownValue, 0x01); + + /// Bit 1 is set to power down overcurrent protection + RTSXDeclareChipRegisterValue(kOCPPowerMask, 0x02); + RTSXDeclareChipRegisterValue(kOCPPowerUpValue, 0x00); + RTSXDeclareChipRegisterValue(kOCPPowerDownValue, 0x02); + + /// Bit 0 and 1 are set to power down SSC and OCP + RTSXDeclareChipRegisterValue(kAllPowerMask, 0x03); + RTSXDeclareChipRegisterValue(kAllPowerUpValue, 0x00); + RTSXDeclareChipRegisterValue(kAllPowerDownValue, 0x03); + } + + +} + +/// Chip registers that control the SSC clock +namespace RTSX::Chip::CLK +{ + RTSXDeclareChipRegister(rCTL, 0xFC02); + namespace CTL + { + RTSXDeclareChipRegisterValue(kLowFrequency, 0x01); + RTSXDeclareChipRegisterValue(kChangeClock, 0x01); + } + + RTSXDeclareChipRegister(rDIV, 0xFC03); + namespace DIV + { + RTSXDeclareChipRegisterValue(k1, 0x01); + RTSXDeclareChipRegisterValue(k2, 0x02); + RTSXDeclareChipRegisterValue(k4, 0x03); + RTSXDeclareChipRegisterValue(k8, 0x04); + } + + RTSXDeclareChipRegister(rSEL, 0xFC04); +} + +/// Chip registers that control the SSC +namespace RTSX::Chip::SSC +{ + RTSXDeclareChipRegister(rDIVN0, 0xFC0F); + RTSXDeclareChipRegister(rDIVN1, 0xFC10); + + RTSXDeclareChipRegister(rCTL1, 0xFC11); + namespace CTL1 + { + RTSXDeclareChipRegisterValue(kRSTB, 0x80); + RTSXDeclareChipRegisterValue(kEnable8x, 0x40); + RTSXDeclareChipRegisterValue(kFixFraction, 0x20); + + RTSXDeclareChipRegisterValue(kSelector1M, 0x00); + RTSXDeclareChipRegisterValue(kSelector2M, 0x08); + RTSXDeclareChipRegisterValue(kSelector4M, 0x10); + RTSXDeclareChipRegisterValue(kSelector8M, 0x18); + } + + RTSXDeclareChipRegister(rCTL2, 0xFC12); + namespace CTL2 + { + RTSXDeclareChipRegisterValue(kDepthMask, 0x07); + RTSXDeclareChipRegisterValue(kDepthDisable, 0x00); + RTSXDeclareChipRegisterValue(kDepth4M, 0x01); + RTSXDeclareChipRegisterValue(kDepth2M, 0x02); + RTSXDeclareChipRegisterValue(kDepth1M, 0x03); + RTSXDeclareChipRegisterValue(kDepth500K, 0x04); + RTSXDeclareChipRegisterValue(kDepth250K, 0x05); + + /// Double the given SSC depth + static inline UInt8 doubleDepth(UInt8 depth) + { + return depth > 1 ? depth - 1 : depth; + } + } +} + +namespace RTSX::Chip +{ + RTSXDeclareChipRegister(rRCCTL, 0xFC14); +} + +/// Chip registers that control the LED behavior +namespace RTSX::Chip::OLT_LED +{ + /// Address of the register that controls the LED + RTSXDeclareChipRegister(rCTL, 0xFC1E); + + /// Special values and flags for the CTL register + namespace CTL + { + /// Bit 3 is set to turn on blinking + RTSXDeclareChipRegisterValue(kBlinkingMask, 0x08); + RTSXDeclareChipRegisterValue(kEnableBlinkingValue, 0x08); + RTSXDeclareChipRegisterValue(kDisableBlinkingValue, 0x00); + } +} + +/// Chip registers that manages GPIO pins +namespace RTSX::Chip::GPIO +{ + /// Address of the register that manages GPIO pins + RTSXDeclareChipRegister(rCTL, 0xFC1F); + + /// Special values and flags for the CTL register + namespace CTL + { + /// Bit 1 is set to turn on the LED + RTSXDeclareChipRegisterValue(kLEDMask, 0x02); + RTSXDeclareChipRegisterValue(kTurnOnLEDValue, 0x02); + RTSXDeclareChipRegisterValue(kTurnOffLEDValue, 0x00); + } +} + +/// Chip registers that manages the card +namespace RTSX::Chip::CARD +{ + RTSXDeclareChipRegister(rCLKSRC, 0xFC2E); + namespace CLKSRC + { + RTSXDeclareChipRegisterValue(kCRCFixClock, 0x00 << 0); + RTSXDeclareChipRegisterValue(kCRCVarClock0, 0x01 << 0); + RTSXDeclareChipRegisterValue(kCRCVarClock1, 0x02 << 0); + + RTSXDeclareChipRegisterValue(kSD30FixClock, 0x00 << 2); + RTSXDeclareChipRegisterValue(kSD30VarClock0, 0x01 << 2); + RTSXDeclareChipRegisterValue(kSD30VarClock1, 0x02 << 2); + + RTSXDeclareChipRegisterValue(kSampleFixClock, 0x00 << 4); + RTSXDeclareChipRegisterValue(kSampleVarClock0, 0x01 << 4); + RTSXDeclareChipRegisterValue(kSampleVarClock1, 0x02 << 4); + } + + RTSXDeclareChipRegister(rPWRCTRL, 0xFD50); + namespace PWRCTRL + { + RTSXDeclareChipRegisterValue(kSDPowerOn, 0x00); + RTSXDeclareChipRegisterValue(kSDPartialPowerOn, 0x01); + RTSXDeclareChipRegisterValue(kSDPowerOff, 0x03); + + RTSXDeclareChipRegisterValue(kSDPowerMask, 0x03); + + RTSXDeclareChipRegisterValue(kSDVCCPartialPowerOn, 0x02); + RTSXDeclareChipRegisterValue(kSDVCCPowerOn, 0x00); + } + + RTSXDeclareChipRegister(rSHAREMODE, 0xFD52); + namespace SHAREMODE + { + RTSXDeclareChipRegisterValue(kMask, 0x0F); + RTSXDeclareChipRegisterValue(kMultiLun, 0x00); + RTSXDeclareChipRegisterValue(kNormal, 0x00); + RTSXDeclareChipRegisterValue(k48SD, 0x04); + RTSXDeclareChipRegisterValue(k48MS, 0x08); + RTSXDeclareChipRegisterValue(kBarossaSD, 0x01); + RTSXDeclareChipRegisterValue(kBarossaMS, 0x02); + } + + /// Address of the register that selects the card drive + RTSXDeclareChipRegister(rDRVSEL, 0xFD53); + namespace DRVSEL + { + RTSXDeclareChipRegisterValue(kMS8mA, 0x01 << 6); + RTSXDeclareChipRegisterValue(kMMC8mA, 0x01 << 4); + RTSXDeclareChipRegisterValue(kXD8mA, 0x01 << 2); + RTSXDeclareChipRegisterValue(kGPIO8mA, 0x01); + + RTSXDeclareChipRegisterValue(kDefault, kMS8mA | kGPIO8mA); + RTSXDeclareChipRegisterValue(kDefault_5209, kMS8mA | kMMC8mA | kXD8mA); + RTSXDeclareChipRegisterValue(kDefault_8411, kMS8mA | kMMC8mA | kXD8mA | kGPIO8mA); + } + + RTSXDeclareChipRegister(rSTOP, 0xFD54); + namespace STOP + { + RTSXDeclareChipRegisterValue(kStopSPI, 0x01); + RTSXDeclareChipRegisterValue(kStopXD, 0x02); + RTSXDeclareChipRegisterValue(kStopSD, 0x04); + RTSXDeclareChipRegisterValue(kStopMS, 0x08); + + RTSXDeclareChipRegisterValue(kClearSPIError, 0x10); + RTSXDeclareChipRegisterValue(kClearXDError, 0x20); + RTSXDeclareChipRegisterValue(kClearSDError, 0x40); + RTSXDeclareChipRegisterValue(kClearMSError, 0x80); + } + + /// Address of the register that enables the card output + RTSXDeclareChipRegister(rOUTPUT, 0xFD55); + + /// Special values and flags for the OUTPUT register + namespace OUTPUT + { + /// Bit 1 is set to enable XD output + RTSXDeclareChipRegisterValue(kXDMask, 1 << 1); + RTSXDeclareChipRegisterValue(kEnableXDValue, 1 << 1); + RTSXDeclareChipRegisterValue(kDisableXDValue, 0); + + /// Bit 2 is set to enable SD output + RTSXDeclareChipRegisterValue(kSDMask, 1 << 2); + RTSXDeclareChipRegisterValue(kEnableSDValue, 1 << 2); + RTSXDeclareChipRegisterValue(kDisableSDValue, 0); + + /// Bit 3 is set to enable MS output + RTSXDeclareChipRegisterValue(kMSMask, 1 << 3); + RTSXDeclareChipRegisterValue(kEnableMSValue, 1 << 3); + RTSXDeclareChipRegisterValue(kDisableMSValue, 0); + + /// Bit 4 is set to enable SPI output + RTSXDeclareChipRegisterValue(kSPIMask, 1 << 4); + RTSXDeclareChipRegisterValue(kEnableSPIValue, 1 << 4); + RTSXDeclareChipRegisterValue(kDisableSPIValue, 0); + } + + RTSXDeclareChipRegister(rDATASRC, 0xFD5B); + namespace DATASRC + { + RTSXDeclareChipRegisterValue(kPingPongBuffer, 0x01); + RTSXDeclareChipRegisterValue(kRingBuffer, 0x00); + RTSXDeclareChipRegisterValue(kMask, 0x01); + } + + RTSXDeclareChipRegister(rSEL, 0xFD5C); + namespace SEL + { + RTSXDeclareChipRegisterValue(kSD, 0x2); + RTSXDeclareChipRegisterValue(kMS, 0x3); + RTSXDeclareChipRegisterValue(kMask, 0x7); + } + + namespace SD30::DRVSEL + { + RTSXDeclareChipRegister(rCLK, 0xFD5A); + namespace CLK + { + RTSXDeclareChipRegisterValue(kDriverTypeA, 0x05); + RTSXDeclareChipRegisterValue(kDriverTypeB, 0x03); + RTSXDeclareChipRegisterValue(kDriverTypeC, 0x02); + RTSXDeclareChipRegisterValue(kDriverTypeD, 0x01); + } + + RTSXDeclareChipRegister(rCMD, 0xFD5E); + + RTSXDeclareChipRegister(rCFG, 0xFD5E); + namespace CFG + { + RTSXDeclareChipRegisterValue(kDriverTypeA, 0x02); + RTSXDeclareChipRegisterValue(kDriverTypeB, 0x03); + RTSXDeclareChipRegisterValue(kDriverTypeC, 0x01); + RTSXDeclareChipRegisterValue(kDriverTypeD, 0x00); + } + + RTSXDeclareChipRegister(rDAT, 0xFD5F); + } + + /// Card pull control registers + namespace PULL + { + RTSXDeclareChipRegister(rCTL1, 0xFD60); + RTSXDeclareChipRegister(rCTL2, 0xFD61); + RTSXDeclareChipRegister(rCTL3, 0xFD62); + RTSXDeclareChipRegister(rCTL4, 0xFD63); + RTSXDeclareChipRegister(rCTL5, 0xFD64); + RTSXDeclareChipRegister(rCTL6, 0xFD65); + } + + /// Register to enable the card clock + RTSXDeclareChipRegister(rCLK, 0xFD69); + namespace CLK + { + RTSXDeclareChipRegisterValue(kDisable, 0x00); + RTSXDeclareChipRegisterValue(kEnableSD, 0x04); + RTSXDeclareChipRegisterValue(kEnableMS, 0x08); + RTSXDeclareChipRegisterValue(kEnableSD40, 0x10); + RTSXDeclareChipRegisterValue(kMask, 0x1E); + } +} + +/// Chip registers that manages the overcurrent protection +namespace RTSX::Chip::OCP +{ + /// Address of the register that controls the overcurrent protection + RTSXDeclareChipRegister(rCTL, 0xFD6A); + + /// Special values and flags for the CTL register + namespace CTL + { + RTSXDeclareChipRegisterValue(kSDClearStatMask, 0x01); + RTSXDeclareChipRegisterValue(kSDClearStatValue, 0x01); + RTSXDeclareChipRegisterValue(kSDClearInterruptMask, 0x02); + RTSXDeclareChipRegisterValue(kSDClearInterruptValue, 0x02); + + RTSXDeclareChipRegisterValue(kSDInterruptMask, 0x04); + RTSXDeclareChipRegisterValue(kSDInterruptValueEnable, 0x04); + RTSXDeclareChipRegisterValue(kSDInterruptValueDisable, 0x00); + + RTSXDeclareChipRegisterValue(kSDDetectionMask, 0x08); + RTSXDeclareChipRegisterValue(kSDDetectionValueEnable, 0x08); + RTSXDeclareChipRegisterValue(kSDDetectionValueDisable, 0x00); + + RTSXDeclareChipRegisterValue(kSDDetectionAndInterruptMask, kSDInterruptMask | kSDDetectionMask); + RTSXDeclareChipRegisterValue(kSDEnableDetectionAndInterruptValue, kSDInterruptValueEnable | kSDDetectionValueEnable) + RTSXDeclareChipRegisterValue(kSDDisableDetectionAndInterruptValue, kSDInterruptValueDisable | kSDDetectionValueDisable) + + + RTSXDeclareChipRegisterValue(kSDVIOClearStatMask, 0x10); + RTSXDeclareChipRegisterValue(kSDVIOClearInterruptMask, 0x20); + RTSXDeclareChipRegisterValue(kSDVIOInterruptMask, 0x40); + RTSXDeclareChipRegisterValue(kSDVIODetectionMask, 0x80); + } + + /// Address of the register that programs the time parameter + RTSXDeclareChipRegister(rPARA1, 0xFD6B); + + /// Special values and flags for the PARA1 register + namespace PARA1 + { + RTSXDeclareChipRegisterValue(kSDVIOTimeMask, 0x70); + RTSXDeclareChipRegisterValue(kSDVIOTimeValue60, 0x00); + RTSXDeclareChipRegisterValue(kSDVIOTimeValue100, 0x10); + RTSXDeclareChipRegisterValue(kSDVIOTimeValue200, 0x20); + RTSXDeclareChipRegisterValue(kSDVIOTimeValue400, 0x30); + RTSXDeclareChipRegisterValue(kSDVIOTimeValue600, 0x40); + RTSXDeclareChipRegisterValue(kSDVIOTimeValue800, 0x50); + RTSXDeclareChipRegisterValue(kSDVIOTimeValue1100, 0x60); + + RTSXDeclareChipRegisterValue(kSDTimeMask, 0x07); + RTSXDeclareChipRegisterValue(kSDTimeValue60, 0x00); + RTSXDeclareChipRegisterValue(kSDTimeValue100, 0x01); + RTSXDeclareChipRegisterValue(kSDTimeValue200, 0x02); + RTSXDeclareChipRegisterValue(kSDTimeValue400, 0x03); + RTSXDeclareChipRegisterValue(kSDTimeValue600, 0x04); + RTSXDeclareChipRegisterValue(kSDTimeValue800, 0x05); + RTSXDeclareChipRegisterValue(kSDTimeValue1100, 0x06); + } + + /// Address of the register that programs the glitch value + RTSXDeclareChipRegister(rGLITCH, 0xFD6C); + + /// Special values and flags for the GLITCH register + namespace GLITCH + { + RTSXDeclareChipRegisterValue(kSDVIOMask, 0xF0); + RTSXDeclareChipRegisterValue(kSDVIOValueNone, 0x00); + RTSXDeclareChipRegisterValue(kSDVIOValue50U, 0x10); + RTSXDeclareChipRegisterValue(kSDVIOValue100U, 0x20); + RTSXDeclareChipRegisterValue(kSDVIOValue200U, 0x30); + RTSXDeclareChipRegisterValue(kSDVIOValue600U, 0x40); + RTSXDeclareChipRegisterValue(kSDVIOValue800U, 0x50); + RTSXDeclareChipRegisterValue(kSDVIOValue1M, 0x60); + RTSXDeclareChipRegisterValue(kSDVIOValue2M, 0x70); + RTSXDeclareChipRegisterValue(kSDVIOValue3M, 0x80); + RTSXDeclareChipRegisterValue(kSDVIOValue4M, 0x90); + RTSXDeclareChipRegisterValue(kSDVIOValue5M, 0xA0); + RTSXDeclareChipRegisterValue(kSDVIOValue6M, 0xB0); + RTSXDeclareChipRegisterValue(kSDVIOValue7M, 0xC0); + RTSXDeclareChipRegisterValue(kSDVIOValue8M, 0xD0); + RTSXDeclareChipRegisterValue(kSDVIOValue9M, 0xE0); + RTSXDeclareChipRegisterValue(kSDVIOValue10M, 0xF0); + + RTSXDeclareChipRegisterValue(kSDMask, 0x0F); + RTSXDeclareChipRegisterValue(kSDValueNone, 0x00); + RTSXDeclareChipRegisterValue(kSDValue50U, 0x01); + RTSXDeclareChipRegisterValue(kSDValue100U, 0x02); + RTSXDeclareChipRegisterValue(kSDValue200U, 0x03); + RTSXDeclareChipRegisterValue(kSDValue600U, 0x04); + RTSXDeclareChipRegisterValue(kSDValue800U, 0x05); + RTSXDeclareChipRegisterValue(kSDValue1M, 0x06); + RTSXDeclareChipRegisterValue(kSDValue2M, 0x07); + RTSXDeclareChipRegisterValue(kSDValue3M, 0x08); + RTSXDeclareChipRegisterValue(kSDValue4M, 0x09); + RTSXDeclareChipRegisterValue(kSDValue5M, 0x0A); + RTSXDeclareChipRegisterValue(kSDValue6M, 0x0B); + RTSXDeclareChipRegisterValue(kSDValue7M, 0x0C); + RTSXDeclareChipRegisterValue(kSDValue8M, 0x0D); + RTSXDeclareChipRegisterValue(kSDValue9M, 0x0E); + RTSXDeclareChipRegisterValue(kSDValue10M, 0x0F); + } + + /// Address of the register that programs the total harmonic distortion parameter + RTSXDeclareChipRegister(rPARA2, 0xFD6D); + + /// Special values and flags for the PARA2 register + namespace PARA2 + { + RTSXDeclareChipRegisterValue(kSDVIOThdMask, 0x70); + RTSXDeclareChipRegisterValue(kSDVIOThdValue190, 0x00); + RTSXDeclareChipRegisterValue(kSDVIOThdValue250, 0x10); + RTSXDeclareChipRegisterValue(kSDVIOThdValue320, 0x20); + RTSXDeclareChipRegisterValue(kSDVIOThdValue380, 0x30); + RTSXDeclareChipRegisterValue(kSDVIOThdValue440, 0x40); + RTSXDeclareChipRegisterValue(kSDVIOThdValue500, 0x50); + RTSXDeclareChipRegisterValue(kSDVIOThdValue570, 0x60); + RTSXDeclareChipRegisterValue(kSDVIOThdValue630, 0x70); + + RTSXDeclareChipRegisterValue(kSDThdMask, 0x07); + RTSXDeclareChipRegisterValue(kSDThdValue450, 0x00); + RTSXDeclareChipRegisterValue(kSDThdValue550, 0x01); + RTSXDeclareChipRegisterValue(kSDThdValue650, 0x02); + RTSXDeclareChipRegisterValue(kSDThdValue750, 0x03); + RTSXDeclareChipRegisterValue(kSDThdValue850, 0x04); + RTSXDeclareChipRegisterValue(kSDThdValue950, 0x05); + RTSXDeclareChipRegisterValue(kSDThdValue1050, 0x06); + RTSXDeclareChipRegisterValue(kSDThdValue1150, 0x07); + + RTSXDeclareChipRegisterValue(kSDThdValue800_524A, 0x04); + RTSXDeclareChipRegisterValue(kSDThdValue800_525A, 0x05); + RTSXDeclareChipRegisterValue(kSDThdValue800_522A, 0x06); + } + + /// Address of the register that contains the current status + RTSXDeclareChipRegister(rSTAT, 0xFD6E); + + /// Special values and flags for the STAT register + namespace STAT + { + /// Bit 1 is set to indicate that SD has an overcurrent event before + RTSXDeclareChipRegisterValue(kSDEver, 1 << 1); + + /// Bit 2 is set to indicate that SD has an overcurrent event now + RTSXDeclareChipRegisterValue(kSDNow, 1 << 2); + + /// Bit 5 is set to indicate that SDVIO has an overcurrent event before + RTSXDeclareChipRegisterValue(kSDVIOEver, 1 << 5); + + /// Bit 6 is set to indicate that SDVIO has an overcurrent event now + RTSXDeclareChipRegisterValue(kSDVIONow, 1 << 6); + } +} + +namespace RTSX::Chip +{ + RTSXDeclareChipRegister(rIRQEN0, 0xFE20); + namespace IRQEN0 + { + RTSXDeclareChipRegisterValue(kEnableLinkDownInt, 0x10); + RTSXDeclareChipRegisterValue(kEnableLinkReadyInt, 0x20); + RTSXDeclareChipRegisterValue(kEnableSuspendInt, 0x40); + RTSXDeclareChipRegisterValue(kEnableDMADoneInt, 0x80); + } + + RTSXDeclareChipRegister(rIRQSTAT0, 0xFE21); + namespace IRQSTAT0 + { + RTSXDeclareChipRegisterValue(kDMADoneInt, 0x80); + RTSXDeclareChipRegisterValue(kSuspendInt, 0x40); + RTSXDeclareChipRegisterValue(kLinkReadyInt, 0x20); + RTSXDeclareChipRegisterValue(kLinkDownInt, 0x10); + } +} + +/// Chip registers that manages DMA +namespace RTSX::Chip::DMA +{ + /// DMA data length registers + RTSXDeclareChipRegister(rC0, 0xFE28); + RTSXDeclareChipRegister(rC1, 0xFE29); + RTSXDeclareChipRegister(rC2, 0xFE2A); + RTSXDeclareChipRegister(rC3, 0xFE2B); + + /// Address of the register that manages DMA + RTSXDeclareChipRegister(rCTL, 0xFE2C); + + /// Special values and flags for the CTL register + namespace CTL + { + /// Bit 7 is set to reset DMA + RTSXDeclareChipRegisterValue(kResetMask, 0x80); + RTSXDeclareChipRegisterValue(kResetValue, 0x80); + RTSXDeclareChipRegisterValue(kBusy, 0x04); + + RTSXDeclareChipRegisterValue(kDirectionToCard, 0x00); + RTSXDeclareChipRegisterValue(kDirectionFromCard, 0x02); + RTSXDeclareChipRegisterValue(kDirectionMask, 0x02); + + RTSXDeclareChipRegisterValue(kEnable, 0x01); + + RTSXDeclareChipRegisterValue(kPackSize128, 0 << 4); + RTSXDeclareChipRegisterValue(kPackSize256, 1 << 4); + RTSXDeclareChipRegisterValue(kPackSize512, 2 << 4); + RTSXDeclareChipRegisterValue(kPackSize1024, 3 << 4); + RTSXDeclareChipRegisterValue(kPackSizeMask, 0x30); + } +} + +namespace RTSX::Chip::RBUF +{ + RTSXDeclareChipRegister(rCTL, 0xFE34); + + /// Special values and flags for the CTL register + namespace CTL + { + /// Bit 7 is set to flush the buffer + RTSXDeclareChipRegisterValue(kFlushMask, 0x80); + RTSXDeclareChipRegisterValue(kFlushValue, 0x80); + } +} + +/// Chip registers that can be programmed to access PHY registers +namespace RTSX::Chip::PHY +{ + /// Address of the register used to set the data direction of accessing PHY registers + RTSXDeclareChipRegister(rRWCTL, 0xFE3C); + + /// Special values and flags for the RWCTL register + namespace RWCTL + { + /// The maximum number of attempts + static constexpr UInt32 kMaximumAttempts = 100000; + + /// Bit 0 is cleared to perform a read operation + static constexpr UInt8 kReadOperation = 0x00; + + /// Bit 0 is set to perform a write operation + static constexpr UInt8 kWriteOperation = 0x01; + + /// Bit 7 is set to indicate that the chip is busy + static constexpr UInt8 kStatusBusy = 0x80; + + /// Register value to start a read operation + static constexpr UInt8 kSetReadOperation = kStatusBusy | kReadOperation; + + /// Register value to start a write operation + static constexpr UInt8 kSetWriteOperation = kStatusBusy | kWriteOperation; + + /// Check whether the device is busy by examining its register value + static inline bool IsBusy(UInt8 value) + { + return (value & kStatusBusy) == kStatusBusy; + } + } + + /// Address of the register that consumes the 1st byte of the data + RTSXDeclareChipRegister(rDATA0, 0xFE3D); + + /// Address of the register that consumes the 2nd byte of the data + RTSXDeclareChipRegister(rDATA1, 0xFE3E); + + /// Address of the register that consumes the address of a PHY register + RTSXDeclareChipRegister(rADDR, 0xFE3F); +} + +namespace RTSX::Chip::MSG +{ + namespace RX + { + RTSXDeclareChipRegister(rDATA0, 0xFE40); + RTSXDeclareChipRegister(rDATA1, 0xFE41); + RTSXDeclareChipRegister(rDATA2, 0xFE42); + RTSXDeclareChipRegister(rDATA3, 0xFE43); + } + + namespace TX + { + RTSXDeclareChipRegister(rDATA0, 0xFE44); + RTSXDeclareChipRegister(rDATA1, 0xFE45); + RTSXDeclareChipRegister(rDATA2, 0xFE46); + RTSXDeclareChipRegister(rDATA3, 0xFE47); + RTSXDeclareChipRegister(rCTL, 0xFE48); + } +} + +namespace RTSX::Chip::LTR +{ + RTSXDeclareChipRegister(rCTL, 0xFE4A); + namespace CTL + { + RTSXDeclareChipRegisterValue(kTxMask, 1 << 7); + RTSXDeclareChipRegisterValue(kEnableTx, 1 << 7); + RTSXDeclareChipRegisterValue(kDisableTx, 0); + + RTSXDeclareChipRegisterValue(kLatencyModeMask, 1 << 6); + RTSXDeclareChipRegisterValue(kLatencyModeHardware, 0); + RTSXDeclareChipRegisterValue(kLatencyModeSoftware, 1 << 6); + } +} + +/// Chip registers that control OOBS polling +namespace RTSX::Chip::OOBS +{ + RTSXDeclareChipRegister(rOFFTIMER, 0xFEA6); + RTSXDeclareChipRegister(rONTIMER, 0xFEA7); + RTSXDeclareChipRegister(rVCMONTIMER, 0xFEA8) + RTSXDeclareChipRegister(rPOLLING, 0xFEA9); + RTSXDeclareChipRegister(rCFG, 0xFF6E); + namespace CFG + { + RTSXDeclareChipRegisterValue(kAutoKDIS, 0x80); + RTSXDeclareChipRegisterValue(kValMask, 0x1F); + } +} + +/// Chip registers that control the ping pong buffer +namespace RTSX::Chip::PPBUF +{ + RTSXDeclareChipRegister(rBASE1, 0xF800); + RTSXDeclareChipRegister(rBASE2, 0xFA00); +} + +namespace RTSX::Chip +{ + RTSXDeclareChipRegister(rPWRGATECTRL, 0xFE75); + namespace PWRGATECTRL + { + RTSXDeclareChipRegisterValue(kOn, 0x00); + RTSXDeclareChipRegisterValue(kEnable, 0x01); + RTSXDeclareChipRegisterValue(kVCC1, 0x02); + RTSXDeclareChipRegisterValue(kVCC2, 0x04); + RTSXDeclareChipRegisterValue(kSuspend, 0x04); + RTSXDeclareChipRegisterValue(kOff, 0x06); + RTSXDeclareChipRegisterValue(kMask, 0x06); + } +} + +namespace RTSX::Chip::SD +{ + RTSXDeclareChipRegister(rVPCLK0CTL, 0xFC2A); + RTSXDeclareChipRegister(rVPCLK1CTL, 0xFC2B); + RTSXDeclareChipRegister(rVPTXCTL, rVPCLK0CTL); + RTSXDeclareChipRegister(rVPRXCTL, rVPCLK1CTL); + namespace VPCTL + { + RTSXDeclareChipRegisterValue(kPhaseSelectMask, 0x1F); + RTSXDeclareChipRegisterValue(kPhaseChange, 0x80); + RTSXDeclareChipRegisterValue(kPhaseNotReset, 0x40); + } + + /// Clock divider, bus mode and width + RTSXDeclareChipRegister(rCFG1, 0xFDA0); + namespace CFG1 + { + RTSXDeclareChipRegisterValue(kClockDivider0, 0x00); + RTSXDeclareChipRegisterValue(kClockDivider128, 0x80); + RTSXDeclareChipRegisterValue(kClockDivider256, 0xC0); + RTSXDeclareChipRegisterValue(kClockDividerMask, 0xC0); + + RTSXDeclareChipRegisterValue(kBusWidth1Bit, 0x00); + RTSXDeclareChipRegisterValue(kBusWidth4Bit, 0x01); + RTSXDeclareChipRegisterValue(kBusWidth8Bit, 0x02); + RTSXDeclareChipRegisterValue(kBusWidthMask, 0x03); + + RTSXDeclareChipRegisterValue(kModeSD20, 0x00); + RTSXDeclareChipRegisterValue(kModeSDDDR, 0x04); + RTSXDeclareChipRegisterValue(kModeSD30, 0x08); + RTSXDeclareChipRegisterValue(kModeMask, 0x0C); + + RTSXDeclareChipRegisterValue(kAsyncFIFONotRST, 0x10); + } + + /// SD command control and response + RTSXDeclareChipRegister(rCFG2, 0xFDA1); + namespace CFG2 + { + RTSXDeclareChipRegisterValue(kCalcCRC7, 0x00); + RTSXDeclareChipRegisterValue(kNoCalcCRC7, 0x80); + + RTSXDeclareChipRegisterValue(kCheckCRC16, 0x00); + RTSXDeclareChipRegisterValue(kNoCheckCRC16, 0x40); + RTSXDeclareChipRegisterValue(kNoCheckWaitCRCTo, 0x20); + + RTSXDeclareChipRegisterValue(kWaitBusyEnd, 0x08); + RTSXDeclareChipRegisterValue(kNoWaitBusyEnd, 0x00); + + RTSXDeclareChipRegisterValue(kCheckCRC7, 0x00); + RTSXDeclareChipRegisterValue(kNoCheckCRC7, 0x04); + + RTSXDeclareChipRegisterValue(kResponseLength0, 0x00); // No response + RTSXDeclareChipRegisterValue(kResponseLength6, 0x01); // 48-bit response + RTSXDeclareChipRegisterValue(kResponseLength17, 0x02); // 136-bit response + + RTSXDeclareChipRegisterValue(kResponseTypeR0, 0x04); + RTSXDeclareChipRegisterValue(kResponseTypeR1, 0x01); + RTSXDeclareChipRegisterValue(kResponseTypeR1b, 0x09); + RTSXDeclareChipRegisterValue(kResponseTypeR2, 0x02); + RTSXDeclareChipRegisterValue(kResponseTypeR3, 0x05); + RTSXDeclareChipRegisterValue(kResponseTypeR4, 0x05); + RTSXDeclareChipRegisterValue(kResponseTypeR5, 0x01); + RTSXDeclareChipRegisterValue(kResponseTypeR6, 0x01); + RTSXDeclareChipRegisterValue(kResponseTypeR7, 0x01); + } + + /// Used by the RTSX USB driver + RTSXDeclareChipRegister(rCFG3, 0xFDA2); + namespace CFG3 + { + RTSXDeclareChipRegisterValue(kEnableSD30ClockEnd, 0x10); + RTSXDeclareChipRegisterValue(kEnableResponse80ClockTimeout, 0x01); + } + + RTSXDeclareChipRegister(rSTAT1, 0xFDA3); + namespace STAT1 + { + RTSXDeclareChipRegisterValue(kCRC7Error, 0x80); + RTSXDeclareChipRegisterValue(kCRC16Error, 0x40); + RTSXDeclareChipRegisterValue(kCRCWriteError, 0x20); + RTSXDeclareChipRegisterValue(kCRCWriteErrorMask, 0x1C); + RTSXDeclareChipRegisterValue(kCRCGetTimeout, 0x02); + RTSXDeclareChipRegisterValue(kTuningCompareError, 0x01); + } + + /// Used by the RTSX USB driver + RTSXDeclareChipRegister(rSTAT2, 0xFDA4); + namespace STAT2 + { + RTSXDeclareChipRegisterValue(kResponse80ClockTimeout, 0x01); + } + + RTSXDeclareChipRegister(rBUSSTAT, 0xFDA5); + namespace BUSSTAT + { + RTSXDeclareChipRegisterValue(kClockToggleEnable, 0x80); + RTSXDeclareChipRegisterValue(kClockForceStop, 0x40); + + // Data line status + RTSXDeclareChipRegisterValue(kData3Status, 0x10); + RTSXDeclareChipRegisterValue(kData2Status, 0x08); + RTSXDeclareChipRegisterValue(kData1Status, 0x04); + RTSXDeclareChipRegisterValue(kData0Status, 0x02); + + // Command line status + RTSXDeclareChipRegisterValue(kCommandStatus, 0x01); + + RTSXDeclareChipRegisterValue(kAllLinesStatus, kCommandStatus | + kData0Status | + kData1Status | + kData2Status | + kData3Status); + } + + RTSXDeclareChipRegister(rPADCTL, 0xFDA6); + namespace PADCTL + { + RTSXDeclareChipRegisterValue(kUse1d8V, 0x80); + RTSXDeclareChipRegisterValue(kUse3d3V, 0x7F); + RTSXDeclareChipRegisterValue(kUseTypeADriving, 0x00); + RTSXDeclareChipRegisterValue(kUseTypeBDriving, 0x01); + RTSXDeclareChipRegisterValue(kUseTypeCDriving, 0x00); + RTSXDeclareChipRegisterValue(kUseTypeDDriving, 0x01); + } + + // Sample point control + RTSXDeclareChipRegister(rSPCTL, 0xFDA7); + namespace SPCTL + { + RTSXDeclareChipRegisterValue(kDDRFixRxData, 0x00); + RTSXDeclareChipRegisterValue(kDDRVarRxData, 0x80); + RTSXDeclareChipRegisterValue(kDDRFixRxDataEdge, 0x00); + RTSXDeclareChipRegisterValue(kDDRFixRxData14Delay, 0x40); + + RTSXDeclareChipRegisterValue(kDDRFixRxCommand, 0x00); + RTSXDeclareChipRegisterValue(kDDRVarRxCommand, 0x20); + RTSXDeclareChipRegisterValue(kDDRFixRxCommandPosEdge, 0x00); + RTSXDeclareChipRegisterValue(kDDRFixRxCommand14Delay, 0x10); + + RTSXDeclareChipRegisterValue(kSD20RxPosEdge, 0x00); + RTSXDeclareChipRegisterValue(kSD20Rx14Delay, 0x08); + RTSXDeclareChipRegisterValue(kSD20RxSelMask, 0x08); + } + + // Push point control + RTSXDeclareChipRegister(rPPCTL, 0xFDA8); + namespace PPCTL + { + RTSXDeclareChipRegisterValue(kDDRFixTxCommandData, 0x00); + RTSXDeclareChipRegisterValue(kDDRVarTxCommandData, 0x80); + + RTSXDeclareChipRegisterValue(kDDRFixTxData14TSU, 0x00); + RTSXDeclareChipRegisterValue(kDDRFixTxData12TSU, 0x40); + + RTSXDeclareChipRegisterValue(kDDRFixTxCommandNegEdge, 0x00); + RTSXDeclareChipRegisterValue(kDDRFixTxCommand14Ahead, 0x20); + + RTSXDeclareChipRegisterValue(kSD20TxNegEdge, 0x00); + RTSXDeclareChipRegisterValue(kSD20Tx14Ahead, 0x10); + RTSXDeclareChipRegisterValue(kSD20TxSelMask, 0x10); + + RTSXDeclareChipRegisterValue(kSSDRVarSDClockPolSwap, 0x01); + } + + RTSXDeclareChipRegister(rCMD0, 0xFDA9); + RTSXDeclareChipRegister(rCMD1, 0xFDAA); + RTSXDeclareChipRegister(rCMD2, 0xFDAB); + RTSXDeclareChipRegister(rCMD3, 0xFDAC); + RTSXDeclareChipRegister(rCMD4, 0xFDAD); + RTSXDeclareChipRegister(rCMD5, 0xFDAE); + + RTSXDeclareChipRegister(rBYTECNTL, 0xFDAF); + RTSXDeclareChipRegister(rBYTECNTH, 0xFDB0); + RTSXDeclareChipRegister(rBLOCKCNTL, 0xFDB1); + RTSXDeclareChipRegister(rBLOCKCNTH, 0xFDB2); + + RTSXDeclareChipRegister(rTRANSFER, 0xFDB3); + namespace TRANSFER + { + /// Transfer Control + RTSXDeclareChipRegisterValue(kTransferStart, 0x80); + RTSXDeclareChipRegisterValue(kTransferEnd, 0x40); + RTSXDeclareChipRegisterValue(kTransferIdle, 0x20); + RTSXDeclareChipRegisterValue(kTransferError, 0x10); + + /// Write one or two bytes from SD_CMD2 and SD_CMD3 to the card + RTSXDeclareChipRegisterValue(kTMNormalWrite, 0x00); + + /// Write `nblocks * blockSize` bytes from the ring buffer to the card + RTSXDeclareChipRegisterValue(kTMAutoWrite3, 0x01); + + /// Write `nblocks * blockSize` bytes from the ring buffer to the card and send a CMD12 when done. + /// The response to the CMD12 is written to the `SD_CMD{0-4}` registers. + RTSXDeclareChipRegisterValue(kTMAutoWrite4, 0x02); + + /// Read `nblocks * blockSize` bytes from the card to the ring buffer + RTSXDeclareChipRegisterValue(kTMAutoRead3, 0x05); + + /// Read `nblocks * blockSize` bytes from the card to the ring buffer and send a CMD12 when done. + /// The response to the CMD12 is written to the `SD_CMD{0-4}` registers. + RTSXDeclareChipRegisterValue(kTMAutoRead4, 0x06); + + /// Send an SD command as specified in the `SD_CMD{0-4}` registers to the card and + /// put the 48-bit response into those registers as well. + /// However, the 136-bit response is put into the ping-pond buffer 2 instead. + RTSXDeclareChipRegisterValue(kTMCmdResp, 0x08); + + /// Send a write command, get the response from the card, + /// write the data from the ring buffer to the card and send a CMD12 when done. + /// The response to the CMD12 is written to the `SD_CMD{0-4}` registers. + RTSXDeclareChipRegisterValue(kTMAutoWrite1, 0x09); + + /// Same as `kTMAutoWrite1` except that no CMD12 is sent. + RTSXDeclareChipRegisterValue(kTMAutoWrite2, 0x0A); + + /// Send a read command and read up to 512 bytes (`nblocks * blockSize`) + /// from the card to the ring buffer or the ping-pong buffer 2. + RTSXDeclareChipRegisterValue(kTMNormalRead, 0x0C); + + /// Same as `kTMAutoWrite1` but perform a read operation + RTSXDeclareChipRegisterValue(kTMAutoRead1, 0x0D); + + /// Same as `kTMAutoWrite2` but perform a read operation + RTSXDeclareChipRegisterValue(kTMAutoRead2, 0x0E); + + /// Send a CMD19, receive a response and the tuning pattern from the card and report the result + RTSXDeclareChipRegisterValue(kTMAutoTuning, 0x0F); + } + + RTSXDeclareChipRegister(rCMDSTATE, 0xFDB5) + namespace CMDSTATE + { + RTSXDeclareChipRegisterValue(kIdle, 0x80); + } + + RTSXDeclareChipRegister(rDATSTATE, 0xFDB6); + namespace DATSTATE + { + RTSXDeclareChipRegisterValue(kIdle, 0x80); + } +} + +namespace RTSX::Chip +{ + RTSXDeclareChipRegister(rDUMMY, 0xFE90); + namespace DUMMY + { + RTSXDeclareChipRegisterValue(kICRevisionMask, 0x0F); + } + + RTSXDeclareChipRegister(rVREF, 0xFE97); + namespace VREF + { + RTSXDeclareChipRegisterValue(kEnablePwdSuspnd, 0x10); + } + + RTSXDeclareChipRegister(rPCLKCTL, 0xFE55); + namespace PCKLCTL + { + RTSXDeclareChipRegisterValue(kModeSelector, 0x20); + } + + RTSXDeclareChipRegister(rPFCTL, 0xFE56); + RTSXDeclareChipRegister(rPFCTL_52XA, 0xFF78); + namespace PFCTL_52XA + { + RTSXDeclareChipRegisterValue(kEfuseBypass, 0x08); + RTSXDeclareChipRegisterValue(kEfusePor, 0x04); + RTSXDeclareChipRegisterValue(kEfusePowerMask, 0x03); + RTSXDeclareChipRegisterValue(kEfusePowerOn, 0x03); + RTSXDeclareChipRegisterValue(kEfusePowerOff, 0x00); + } + + RTSXDeclareChipRegister(rAFCTL, 0xFE57); + namespace AFCTL + { + RTSXDeclareChipRegisterValue(kCTL0, 0x10); + RTSXDeclareChipRegisterValue(kCTL1, 0x20); + RTSXDeclareChipRegisterValue(kValueMask, 0x03); + RTSXDeclareChipRegisterValue(kDisableASPM, 0x00); + RTSXDeclareChipRegisterValue(kEnableASPML0, 0x01); + RTSXDeclareChipRegisterValue(kEnableASPML1, 0x02); + } + + RTSXDeclareChipRegister(rPMCLKFCTL, 0xFE58); + namespace PMCLKFCTL + { + RTSXDeclareChipRegisterValue(kEnableClockPM, 0x01); + } + + RTSXDeclareChipRegister(rFCTL, 0xFE59); + namespace FCTL + { + RTSXDeclareChipRegisterValue(kUpmeXmtDebug, 0x02); + } + + RTSXDeclareChipRegister(rLINKSTA, 0xFE5B); + + RTSXDeclareChipRegister(rPGWIDTH, 0xFE5C); + + RTSXDeclareChipRegister(rHSSTA, 0xFE60); + namespace HSSTA + { + RTSXDeclareChipRegisterValue(kHostWakeup, 0); + RTSXDeclareChipRegisterValue(kHostEnterS1, 1); + RTSXDeclareChipRegisterValue(kHostEnterS3, 2); + RTSXDeclareChipRegisterValue(kMask, 3); + } + + RTSXDeclareChipRegister(rPEDBG, 0xFE71); + namespace PEDBG + { + RTSXDeclareChipRegisterValue(kDebug0, 0x08); + } + + RTSXDeclareChipRegister(rNFTSTXCTL, 0xFE72); +} + +namespace RTSX::Chip::L1SUB +{ + RTSXDeclareChipRegister(rCFG1, 0xFE8D); + namespace CFG1 + { + RTSXDeclareChipRegisterValue(kAuxClockActiveSelectorMask, 0x01); + RTSXDeclareChipRegisterValue(kMacCkswDone, 0x00); + } + + RTSXDeclareChipRegister(rCFG2, 0xFE8E); + namespace CFG2 + { + RTSXDeclareChipRegisterValue(kAutoConfig, 0x02); + } + + RTSXDeclareChipRegister(rCFG3, 0xFE8F); + namespace CFG3 + { + RTSXDeclareChipRegisterValue(kMBIAS2Enable_5250, 1 << 7); + } +} + +namespace RTSX::Chip +{ + RTSXDeclareChipRegister(rPETXCFG, 0xFF03); + namespace PETXCFT + { + RTSXDeclareChipRegisterValue(kForceClockRequestDelinkMask, 1 << 7); + RTSXDeclareChipRegisterValue(kForceClockRequestLow, 0x80); + RTSXDeclareChipRegisterValue(kForceClockRequestHigh, 0x00); + } + + RTSXDeclareChipRegister(rRREFCFG, 0xFF6C); + namespace RREFCFG + { + RTSXDeclareChipRegisterValue(kVBGSelectorMask, 0x38); + RTSXDeclareChipRegisterValue(kVBGSelector1V25, 0x28); + } +} + +namespace RTSX::Chip::PM +{ + RTSXDeclareChipRegister(rCTRL1, 0xFF44); + namespace CTRL1 + { + RTSXDeclareChipRegisterValue(kCDResumeEnableMask, 0xF0); + } + + RTSXDeclareChipRegister(rCTRL2, 0xFF45); + + RTSXDeclareChipRegister(rCTRL3, 0xFF46); + namespace CTRL3 + { + RTSXDeclareChipRegisterValue(kEnableSDIOSendPME, 0x80); + RTSXDeclareChipRegisterValue(kForceRcModeOn, 0x40); + RTSXDeclareChipRegisterValue(kForceRx50LinkOn, 0x20); + RTSXDeclareChipRegisterValue(kEnableD3DelinkMode, 0x10); + RTSXDeclareChipRegisterValue(kUsePesrtbControlDelink, 0x08); + RTSXDeclareChipRegisterValue(kDelayPinWake, 0x04); + RTSXDeclareChipRegisterValue(kResetPinWake, 0x02); + RTSXDeclareChipRegisterValue(kEnablePmWake, 0x01); + } + RTSXDeclareChipRegister(rCTRL3_52XA, 0xFF7E); // RTS524A, RTS525A + + RTSXDeclareChipRegister(rCTRL4, 0xFF47); +} + +namespace RTSX::Chip::LDO +{ + RTSXDeclareChipRegister(rPWRSEL, 0xFE78); + + namespace DV18 + { + RTSXDeclareChipRegister(rCFG, 0xFF70); + namespace CFG + { + RTSXDeclareChipRegisterValue(kSRMask, 0xC0); + RTSXDeclareChipRegisterValue(kSRDefault, 0x40); + RTSXDeclareChipRegisterValue(kDV331812Mask, 0x70); + RTSXDeclareChipRegisterValue(kDV331812_3V3, 0x70); + RTSXDeclareChipRegisterValue(kDV331812_1V7, 0x30); + } + } + + RTSXDeclareChipRegister(rCFG2, 0xFF71); + namespace CFG2 + { + RTSXDeclareChipRegisterValue(kD3318Mask, 0x07); + RTSXDeclareChipRegisterValue(kD33183d3V, 0x07); + RTSXDeclareChipRegisterValue(kD33181d8V, 0x02); + + RTSXDeclareChipRegisterValue(kDV331812VDD1, 0x04); + RTSXDeclareChipRegisterValue(kDV331812PowerOn, 0x08); + RTSXDeclareChipRegisterValue(kDV331812PowerOff, 0x00); + } + + namespace VCC + { + RTSXDeclareChipRegister(rCFG0, 0xFF72); + namespace CFG0 + { + RTSXDeclareChipRegisterValue(kLMTVTHMask, 0x30); + RTSXDeclareChipRegisterValue(kLMTVTH2A, 0x10); + } + + RTSXDeclareChipRegister(rCFG1, 0xFF73); + namespace CFG1 + { + RTSXDeclareChipRegisterValue(kRefTuneMask, 0x30); + RTSXDeclareChipRegisterValue(kRef1d2V, 0x20); + RTSXDeclareChipRegisterValue(kTuneMask, 0x07); + RTSXDeclareChipRegisterValue(k1d8V, 0x04); + RTSXDeclareChipRegisterValue(k3d3V, 0x07); + RTSXDeclareChipRegisterValue(kLimitEnable, 0x08); + } + } + + namespace VIO + { + RTSXDeclareChipRegister(rCFG, 0xFF75); + namespace CFG + { + RTSXDeclareChipRegisterValue(kSRMask, 0xC0); + RTSXDeclareChipRegisterValue(kSRDefault, 0x40); + RTSXDeclareChipRegisterValue(kRefTuneMask, 0x30); + RTSXDeclareChipRegisterValue(kRef1d2V, 0x20); + RTSXDeclareChipRegisterValue(kTuneMask, 0x07); + RTSXDeclareChipRegisterValue(k1d7V, 0x03); + RTSXDeclareChipRegisterValue(k1d8V, 0x04); + RTSXDeclareChipRegisterValue(k3d3V, 0x07); + } + } + + namespace DV12S + { + RTSXDeclareChipRegister(rCFG, 0xFF76); + namespace CFG + { + RTSXDeclareChipRegisterValue(kRef12TuneMask, 0x18); + RTSXDeclareChipRegisterValue(kRef12TuneDefault, 0x10); + RTSXDeclareChipRegisterValue(kD12TuneMask, 0x07); + RTSXDeclareChipRegisterValue(kD12TuneDefault, 0x04); + } + } + + namespace AV12S + { + RTSXDeclareChipRegister(rCFG, 0xFF77); + namespace CFG + { + RTSXDeclareChipRegisterValue(kTuneMask, 0x07); + RTSXDeclareChipRegisterValue(kTuneDefault, 0x04); + } + } + + RTSXDeclareChipRegister(rCTL1, 0xFE7D); + namespace CTL1 + { + RTSXDeclareChipRegisterValue(kSD40VIOTuneMask, 0x70); + RTSXDeclareChipRegisterValue(kSD40VIOTune1V7, 0x30); + RTSXDeclareChipRegisterValue(kSDVIO1V8, 0x40); + RTSXDeclareChipRegisterValue(kSDVIO3V3, 0x70); + } +} + +namespace RTSX::Chip +{ + RTSXDeclareChipRegister(rCLKCFG3_525A, 0xFF79); + namespace CLKCFG3_525A + { + RTSXDeclareChipRegisterValue(kMemPD, 0xF0); + } +} + +// +// MARK: - 3. PHY Registers +// + +namespace RTSX::PHYS +{ + RTSXDeclarePhysRegister(rPCR, 0x00); + namespace PCR + { + RTSXDeclarePhysRegisterValue(kForceCode, 0xB000); + RTSXDeclarePhysRegisterValue(kOOBSCali50, 0x0800); + RTSXDeclarePhysRegisterValue(kOOBSVCM08, 0x0200); + RTSXDeclarePhysRegisterValue(kOOBSSEN90, 0x0040); + RTSXDeclarePhysRegisterValue(kEnableRSSI, 0x0002); + RTSXDeclarePhysRegisterValue(kRx10K, 0x0001); + } + + /// Address of the register that is required to enable or disable OOBS polling + RTSXDeclarePhysRegister(rRCR0, 0x01); + namespace RCR0 + { + /// Bit 9 is set to enable OOBS polling + static constexpr UInt32 kOOBSControlBitIndex = 9; + } + + RTSXDeclarePhysRegister(rRCR1, 0x02); + namespace RCR1 + { + RTSXDeclarePhysRegisterValue(kADPTime4, 0x0400); + RTSXDeclarePhysRegisterValue(kVCOCoarse, 0x001F); + RTSXDeclarePhysRegisterValue(kInit27s, 0x0A1F); + } + + RTSXDeclarePhysRegister(rSSCCR2, 0x02); + namespace SSCCR2 + { + RTSXDeclarePhysRegisterValue(kPLLNcode, 0x0A00); + RTSXDeclarePhysRegisterValue(kTime0, 0x001C); + RTSXDeclarePhysRegisterValue(kTime2Width, 0x0003); + } + + RTSXDeclarePhysRegister(rSSCCR3, 0x03); + namespace SSCCR3 + { + RTSXDeclarePhysRegisterValue(kStepIn, 0x2740); + RTSXDeclarePhysRegisterValue(kCheckDelay, 0x0008); + } + + RTSXDeclarePhysRegister(rANA3, 0x03); + namespace ANA3 + { + RTSXDeclarePhysRegisterValue(kTimerMax, 0x2700); + RTSXDeclarePhysRegisterValue(kOOBSDebugEnable, 0x0040); + RTSXDeclarePhysRegisterValue(kCMUDebugEnable, 0x0008); + } + + RTSXDeclarePhysRegister(rRCR2, 0x03) + namespace RCR2 + { + RTSXDeclarePhysRegisterValue(kEnableEmphase, 0x8000); + RTSXDeclarePhysRegisterValue(kNADJR, 0x4000); + RTSXDeclarePhysRegisterValue(kCDRSR2, 0x0100); + RTSXDeclarePhysRegisterValue(kFreqSel12, 0x0040); + RTSXDeclarePhysRegisterValue(kCDRSC12P, 0x0010); + RTSXDeclarePhysRegisterValue(kCalibLate, 0x0002); + RTSXDeclarePhysRegisterValue(kInit27s, 0xC152); + } + + RTSXDeclarePhysRegister(rRDR, 0x05); + namespace RDR + { + RTSXDeclarePhysRegisterValue(kRxDSEL19, 0x4000); + RTSXDeclarePhysRegisterValue(kSSCAutoPwd, 0x0600); + } + + RTSXDeclarePhysRegister(rTUNE, 0x08); + namespace TUNE + { + RTSXDeclarePhysRegisterValue(kTuneRef10, 0x4000); + RTSXDeclarePhysRegisterValue(kVBGSel1252, 0x0C00); + RTSXDeclarePhysRegisterValue(kSDBus33, 0x0200); + RTSXDeclarePhysRegisterValue(kTuneD18, 0x01C0); + RTSXDeclarePhysRegisterValue(kTuneD12, 0x0020); + RTSXDeclarePhysRegisterValue(kTuneA12, 0x0004); + RTSXDeclarePhysRegisterValue(kVoltageMask, 0xFC3F); + RTSXDeclarePhysRegisterValue(kVoltage3V3, 0x03C0); + RTSXDeclarePhysRegisterValue(kD18_1V8, 0x0100); + RTSXDeclarePhysRegisterValue(kD18_1V7, 0x0080); + } + + RTSXDeclarePhysRegister(rANA8, 0x08); + namespace ANA8 + { + RTSXDeclarePhysRegisterValue(kRxEQDCGain, 0x5000); + RTSXDeclarePhysRegisterValue(kSelRxEnable, 0x0400); + RTSXDeclarePhysRegisterValue(kRxEQValue, 0x03C0); + RTSXDeclarePhysRegisterValue(kSCP, 0x0020); + RTSXDeclarePhysRegisterValue(kSelIPI, 0x0004); + } + + RTSXDeclarePhysRegister(rBPCR, 0x0A); + namespace BPCR + { + RTSXDeclarePhysRegisterValue(kIBRxSel, 0x0400); + RTSXDeclarePhysRegisterValue(kIBTxSel, 0x0100); + RTSXDeclarePhysRegisterValue(kIBFilter, 0x0080); + RTSXDeclarePhysRegisterValue(kEnableCMirror, 0x0040); + } + + RTSXDeclarePhysRegister(rBACR, 0x11); + namespace BACR + { + RTSXDeclarePhysRegisterValue(kBasicMask, 0xFFF3); + } + + RTSXDeclarePhysRegister(rREV, 0x19); + namespace REV + { + RTSXDeclarePhysRegisterValue(kRESV, 0xE000); + RTSXDeclarePhysRegisterValue(kRxIdleLatched, 0x1000); + RTSXDeclarePhysRegisterValue(kEnableP1, 0x0800); + RTSXDeclarePhysRegisterValue(kEnableRxIdle, 0x0400); + RTSXDeclarePhysRegisterValue(kEnableTxClockRequest, 0x0200); + RTSXDeclarePhysRegisterValue(kEnableRxClockRequest, 0x0100); + RTSXDeclarePhysRegisterValue(kClockRequestDT10, 0x0040); + RTSXDeclarePhysRegisterValue(kStopClockRD, 0x0020); + RTSXDeclarePhysRegisterValue(kRxPWST, 0x0008); + RTSXDeclarePhysRegisterValue(kStopClockWR, 0x0004); + + /// Get the value for RTS5249 + static constexpr UInt16 value5249() + { + return kRESV | kRxIdleLatched | kEnableP1 | kEnableRxIdle | + kEnableTxClockRequest | kClockRequestDT10 | kStopClockRD | kRxPWST | kStopClockWR; + } + } + + RTSXDeclarePhysRegister(rREV0, 0x19); + namespace REV0 + { + RTSXDeclarePhysRegisterValue(kFilterOut, 0x3800); + RTSXDeclarePhysRegisterValue(kCDRBypassPFD, 0x0100); + RTSXDeclarePhysRegisterValue(kCDRRxIdleBypass, 0x0002); + } + + RTSXDeclarePhysRegister(rANA1A, 0x1A); + namespace ANA1A + { + RTSXDeclarePhysRegisterValue(kTXRLoopback, 0x2000); + RTSXDeclarePhysRegisterValue(kRXTBist, 0x0500); + RTSXDeclarePhysRegisterValue(kTXRBist, 0x0040); + RTSXDeclarePhysRegisterValue(kRev, 0x0006); + RTSXDeclarePhysRegisterValue(kInit27s, 0x2546); + } + + RTSXDeclarePhysRegister(rANA1D, 0x1D); + namespace ANA1D + { + RTSXDeclarePhysRegisterValue(kDebugAddress, 0x0004); + } + + RTSXDeclarePhysRegister(rFLD0_525A, 0x1D); + namespace FLD0_525A + { + RTSXDeclarePhysRegisterValue(kClkReq20c, 0x8000); + RTSXDeclarePhysRegisterValue(kRxIdleEnable, 0x1000); + RTSXDeclarePhysRegisterValue(kBitErrRstn, 0x0800); + RTSXDeclarePhysRegisterValue(kBerCount, 0x01E0); + RTSXDeclarePhysRegisterValue(kBerTimer, 0x001E); + RTSXDeclarePhysRegisterValue(kCheckEnable, 0x0001); + } + + RTSXDeclarePhysRegister(rFLD3, 0x1D); + namespace FLD3 + { + RTSXDeclarePhysRegisterValue(kTimer4, 0x0800); + RTSXDeclarePhysRegisterValue(kTimer6, 0x0020); + RTSXDeclarePhysRegisterValue(kRxDelink, 0x0004); + RTSXDeclarePhysRegisterValue(kInit27s, 0x0004); + } + + RTSXDeclarePhysRegister(rFLD4, 0x1E); + namespace FLD4 + { + RTSXDeclarePhysRegisterValue(kFLDENSel, 0x4000); + RTSXDeclarePhysRegisterValue(kReqRef, 0x2000); + RTSXDeclarePhysRegisterValue(kRxAmpOff, 0x1000); + RTSXDeclarePhysRegisterValue(kReqADDA, 0x0800); + RTSXDeclarePhysRegisterValue(kBerCount, 0x00E0); + RTSXDeclarePhysRegisterValue(kBerTimer, 0x000A); + RTSXDeclarePhysRegisterValue(kBerCheckEnable, 0x0001); + RTSXDeclarePhysRegisterValue(kInit27s, 0x5C7F); + } + + RTSXDeclarePhysRegister(rDIG1E, 0x1E); + namespace DIG1E + { + RTSXDeclarePhysRegisterValue(kRev, 0x4000); + RTSXDeclarePhysRegisterValue(kD0XD1, 0x1000); + RTSXDeclarePhysRegisterValue(kRxOnHost, 0x0800); + RTSXDeclarePhysRegisterValue(kRCLKRefHost, 0x0400); + RTSXDeclarePhysRegisterValue(kRCLKTxEnableKeep, 0x0040); + RTSXDeclarePhysRegisterValue(kRCLKTxTermKeep, 0x0020); + RTSXDeclarePhysRegisterValue(kRCLKRxEIdleOn, 0x0010); + RTSXDeclarePhysRegisterValue(kTxTermKeep, 0x0008); + RTSXDeclarePhysRegisterValue(kRxTermKeep, 0x0004); + RTSXDeclarePhysRegisterValue(kTxEnableKeep, 0x0002); + RTSXDeclarePhysRegisterValue(kRxEnableKeep, 0x0001); + } +} + +// +// MARK: - 4. PCI Config Registers +// + +namespace RTSX::PCR +{ + static constexpr IOByteCount kSREG1 = 0x724; + static constexpr IOByteCount kSREG2 = 0x814; + static constexpr IOByteCount kSREG3 = 0x747; +} + +// PCI Config L1SS (From Linux) +#define PCI_L1SS_CTL1 0x08 /* Control 1 Register */ +#define PCI_L1SS_CTL1_PCIPM_L1_2 0x00000001 /* PCI-PM L1.2 Enable */ +#define PCI_L1SS_CTL1_PCIPM_L1_1 0x00000002 /* PCI-PM L1.1 Enable */ +#define PCI_L1SS_CTL1_ASPM_L1_2 0x00000004 /* ASPM L1.2 Enable */ +#define PCI_L1SS_CTL1_ASPM_L1_1 0x00000008 /* ASPM L1.1 Enable */ +#define PCI_L1SS_CTL1_L1_2_MASK 0x00000005 +#define PCI_L1SS_CTL1_L1SS_MASK 0x0000000f +#define PCI_L1SS_CTL1_CM_RESTORE_TIME 0x0000ff00 /* Common_Mode_Restore_Time */ +#define PCI_L1SS_CTL1_LTR_L12_TH_VALUE 0x03ff0000 /* LTR_L1.2_THRESHOLD_Value */ +#define PCI_L1SS_CTL1_LTR_L12_TH_SCALE 0xe0000000 /* LTR_L1.2_THRESHOLD_Scale */ + +// PCIe Capability (From Linux) +#define PCI_EXP_DEVCTL2 40 /* Device Control 2 */ +#define PCI_EXP_DEVCTL2_COMP_TIMEOUT 0x000f /* Completion Timeout Value */ +#define PCI_EXP_DEVCTL2_COMP_TMOUT_DIS 0x0010 /* Completion Timeout Disable */ +#define PCI_EXP_DEVCTL2_ARI 0x0020 /* Alternative Routing-ID */ +#define PCI_EXP_DEVCTL2_ATOMIC_REQ 0x0040 /* Set Atomic requests */ +#define PCI_EXP_DEVCTL2_ATOMIC_EGRESS_BLOCK 0x0080 /* Block atomic egress */ +#define PCI_EXP_DEVCTL2_IDO_REQ_EN 0x0100 /* Allow IDO for requests */ +#define PCI_EXP_DEVCTL2_IDO_CMP_EN 0x0200 /* Allow IDO for completions */ +#define PCI_EXP_DEVCTL2_LTR_EN 0x0400 /* Enable LTR mechanism */ +#define PCI_EXP_DEVCTL2_OBFF_MSGA_EN 0x2000 /* Enable OBFF Message type A */ +#define PCI_EXP_DEVCTL2_OBFF_MSGB_EN 0x4000 /* Enable OBFF Message type B */ +#define PCI_EXP_DEVCTL2_OBFF_WAKE_EN 0x6000 /* OBFF using WAKE# signaling */ + +#endif /* Registers_hpp */ diff --git a/RealtekPCIeCardReader/SD.hpp b/RealtekPCIeCardReader/SD.hpp new file mode 100644 index 0000000..bb167fe --- /dev/null +++ b/RealtekPCIeCardReader/SD.hpp @@ -0,0 +1,248 @@ +// +// SD.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 5/27/21. +// + +#ifndef SD_hpp +#define SD_hpp + +#include +#include "Utilities.hpp" + +// TODO: TO C++ +/* + MMC status in R1, for native mode (SPI bits are different) + Type + e : error bit + s : status bit + r : detected and set for the actual command response + x : detected and set during command execution. the host must poll + the card by sending status command in order to read these bits. + Clear condition + a : according to the card state + b : always related to the previous command. Reception of + a valid command will clear it (with a delay of one command) + c : clear by read + */ + +// TODO: Convert to C++ +#define R1_OUT_OF_RANGE (1 << 31) /* er, c */ +#define R1_ADDRESS_ERROR (1 << 30) /* erx, c */ +#define R1_BLOCK_LEN_ERROR (1 << 29) /* er, c */ +#define R1_ERASE_SEQ_ERROR (1 << 28) /* er, c */ +#define R1_ERASE_PARAM (1 << 27) /* ex, c */ +#define R1_WP_VIOLATION (1 << 26) /* erx, c */ +#define R1_CARD_IS_LOCKED (1 << 25) /* sx, a */ +#define R1_LOCK_UNLOCK_FAILED (1 << 24) /* erx, c */ +#define R1_COM_CRC_ERROR (1 << 23) /* er, b */ +#define R1_ILLEGAL_COMMAND (1 << 22) /* er, b */ +#define R1_CARD_ECC_FAILED (1 << 21) /* ex, c */ +#define R1_CC_ERROR (1 << 20) /* erx, c */ +#define R1_ERROR (1 << 19) /* erx, c */ +#define R1_UNDERRUN (1 << 18) /* ex, c */ +#define R1_OVERRUN (1 << 17) /* ex, c */ +#define R1_CID_CSD_OVERWRITE (1 << 16) /* erx, c, CID/CSD overwrite */ +#define R1_WP_ERASE_SKIP (1 << 15) /* sx, c */ +#define R1_CARD_ECC_DISABLED (1 << 14) /* sx, a */ +#define R1_ERASE_RESET (1 << 13) /* sr, c */ +#define R1_STATUS(x) (x & 0xFFF9A000) +#define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ +#define R1_READY_FOR_DATA (1 << 8) /* sx, a */ +#define R1_SWITCH_ERROR (1 << 7) /* sx, c */ +#define R1_EXCEPTION_EVENT (1 << 6) /* sr, a */ +#define R1_APP_CMD (1 << 5) /* sr, c */ + +/// Represents the 48-bit R1 response +struct PACKED SDResponse1 +{ +private: + /// The start bit (must be 0) + UInt8 start: 1; + + /// The transmission bit (must be 0) + UInt8 transmission: 1; + + /// The command index + UInt8 command: 6; + + /// The card status (little endian) + /// @warning The card status in the response buffer is encoded in big endian. + /// The caller should use `getStatus()` to fetch the correct value. + UInt32 status; + + /// The CRC7 value + UInt8 crc7: 7; + + /// The end bit (must be 1) + UInt8 end: 1; + +public: + /// Get the card status + inline UInt32 getStatus() const + { + return OSSwapInt32(this->status); + } +}; + +/// Represents the 48-bit R1b response +struct PACKED SDResponse1b +{ +private: + /// The start bit (must be 0) + UInt8 start: 1; + + /// The transmission bit (must be 0) + UInt8 transmission: 1; + + /// The command index + UInt8 command: 6; + + /// The card status (little endian) + /// @warning The card status in the response buffer is encoded in big endian. + /// The caller should use `getStatus()` to fetch the correct value. + UInt32 status; + + /// The CRC7 value + UInt8 crc7: 7; + + /// The end bit (must be 1) + UInt8 end: 1; + +public: + /// Get the card status + inline UInt32 getStatus() const + { + return OSSwapInt32(this->status); + } +}; + +/// Represents the 136-bit R2 response +struct PACKED SDResponse2 +{ + /// The start bit (must be 0) + UInt8 start: 1; + + /// The transmission bit (must be 0) + UInt8 transmission: 1; + + /// The reserved 6 bits (all set to 1) + UInt8 reserved: 6; + + /// The register value including the CRC7 value and the end bit + UInt8 value[16]; +}; + +/// Represents the 48-bit R3 response +struct PACKED SDResponse3 +{ +private: + /// The start bit (must be 0) + UInt8 start: 1; + + /// The transmission bit (must be 0) + UInt8 transmission: 1; + + /// The reserved 6 bits (all set to 1) + UInt8 reserved: 6; + + /// The register value (little endian) + /// @warning The OCR value in the response buffer is encoded in big endian. + /// The caller should use `getValue()` to fetch the correct value. + UInt32 value; + + /// The reserved 7 bits and the end bit (all set to 1) + UInt8 end; + +public: + /// Get the register value + inline UInt32 getValue() const + { + return OSSwapInt32(this->value); + } +}; + +/// Represents the 48-bit R6 response +struct PACKED SDResponse6 +{ +private: + /// The start bit (must be 0) + UInt8 start: 1; + + /// The transmission bit (must be 0) + UInt8 transmission: 1; + + /// The command index + UInt8 command: 6; + + /// The RCA value (little endian) + UInt16 rca; + + /// The card status (Bits 23, 22, 19, 12:0) + UInt16 status; + + /// The CRC7 value + UInt8 crc7: 7; + + /// The end bit (must be 1) + UInt8 end: 1; + +public: + /// Get the card relative address + inline UInt16 getRCA() const + { + return OSSwapInt16(this->rca); + } +}; + +/// Represents the 48-bit R7 response +struct PACKED SDResponse7 +{ + /// The start bit (must be 0) + UInt8 start: 1; + + /// The transmission bit (must be 0) + UInt8 transmission: 1; + + /// The command index + UInt8 command: 6; + + /// The reserved 18 bits (all set to 0) + UInt32 reserved: 18; + + /// Indicate whether the card supports VDD3 (PCIe 1.2V Support) + bool supports1d2V: 1; + + /// Indicate whether the card responds PCIe acceptance + bool acceptsPCIe: 1; + + /// + /// Indicate the range of voltages that the card supports + /// + /// @note Possible values: 0b0000: Not defined + /// 0b0001: 2.7V - 3.6V + /// 0b0010: Reserved for Low Voltage Range + /// 0b0100: Reserved + /// 0b1000: Reserved + /// Others: Not defined + /// + UInt8 supportedVoltages: 4; + + /// Echo-back of the check pattern + UInt8 checkPattern; + + /// The CRC7 value + UInt8 crc7: 7; + + /// The end bit (must be 1) + UInt8 end: 1; +}; + +static_assert(sizeof(SDResponse1) == 06, "ABI Error: R1 must be 48-bit long."); +static_assert(sizeof(SDResponse2) == 17, "ABI Error: R2 must be 136-bit long."); +static_assert(sizeof(SDResponse3) == 06, "ABI Error: R3 must be 48-bit long."); +static_assert(sizeof(SDResponse6) == 06, "ABI Error: R6 must be 48-bit long."); +static_assert(sizeof(SDResponse7) == 06, "ABI Error: R7 must be 48-bit long."); + +#endif /* SD_hpp */ diff --git a/RealtekPCIeCardReader/Utilities.hpp b/RealtekPCIeCardReader/Utilities.hpp new file mode 100644 index 0000000..f1e2791 --- /dev/null +++ b/RealtekPCIeCardReader/Utilities.hpp @@ -0,0 +1,214 @@ +// +// Utilities.hpp +// RealtekPCIeCardReader +// +// Created by FireWolf on 2/27/21. +// + +#ifndef Utilities_hpp +#define Utilities_hpp + +#include +#include "Debug.hpp" + +/// Get the number of elements in an array +template +constexpr size_t arrsize(const T (&array)[N]) +{ + return N; +} + +template +struct Pair +{ + T1 first; + + T2 second; + + Pair(T1 first, T2 second) + : first(first), second(second) {} +}; + +static inline UInt16 IOPCIeDeviceConfigRead16(IOPCIDevice* device, IOByteCount offset) +{ + UInt32 base = device->findPCICapability(kIOPCICapabilityIDPCIExpress); + + passert(base != 0, "The given device is not a PCIe device."); + + return device->configRead16(base + offset); +} + +static inline void IOPCIeDeviceConfigWrite16(IOPCIDevice* device, IOByteCount offset, UInt16 value) +{ + UInt32 base = device->findPCICapability(kIOPCICapabilityIDPCIExpress); + + passert(base != 0, "The given device is not a PCIe device."); + + device->configWrite16(base + offset, value); +} + +static inline void IOPCIeDeviceConfigSet16(IOPCIDevice* device, IOByteCount offset, UInt16 set) +{ + UInt32 base = device->findPCICapability(kIOPCICapabilityIDPCIExpress); + + passert(base != 0, "The given device is not a PCIe device."); + + UInt16 value = device->configRead16(base + offset) | set; + + device->configWrite16(base + offset, value); +} + +static inline void IOPCIeDeviceConfigClear16(IOPCIDevice* device, IOByteCount offset, UInt16 clear) +{ + UInt32 base = device->findPCICapability(kIOPCICapabilityIDPCIExpress); + + passert(base != 0, "The given device is not a PCIe device."); + + UInt16 value = device->configRead16(base + offset) & (~clear); + + device->configWrite16(base + offset, value); +} + +#define KPTR(ptr) \ + static_cast(reinterpret_cast(ptr) >> 32), \ + static_cast(reinterpret_cast(ptr)) + +template +constexpr T MHz2Hz(T mhz) +{ + return mhz * 1000000; +} + +template +constexpr T KHz2Hz(T khz) +{ + return khz * 1000; +} + +template +constexpr T Hz2MHz(T hz) +{ + return hz / 1000000; +} + +template +bool isOneOf(T value, Ts... args) +{ + return ((value == args) || ...); +} + +template +bool isNotOneOf(T value, Ts... args) +{ + return ((value != args) && ...); +} + +static inline const char* YESNO(bool value) +{ + return value ? "Yes" : "No"; +} + +template +T MIN(T lhs, T rhs) +{ + return lhs < rhs ? lhs : rhs; +} + +template +T MAX(T lhs, T rhs) +{ + return lhs < rhs ? rhs : lhs; +} + +static inline bool OSDictionaryAddStringToDictionary(OSDictionary* dictionary, const char* key, const char* value) +{ + OSString* v = OSString::withCString(value); + + if (v == nullptr) + { + return false; + } + + dictionary->setObject(key, v); + + v->release(); + + return true; +} + +static inline bool OSDictionaryAddDataToDictionary(OSDictionary* dictionary, const char* key, const void* bytes, IOByteCount length) +{ + OSData* data = OSData::withBytes(bytes, static_cast(length)); + + if (data == nullptr) + { + return false; + } + + dictionary->setObject(key, data); + + data->release(); + + return true; +} + +template +static inline bool OSDictionaryAddDataToDictionary(OSDictionary* dictionary, const char* key, const UInt8 (&bytes)[N]) +{ + return OSDictionaryAddDataToDictionary(dictionary, key, bytes, N); +} + +/** + * Export function or symbol for linking + */ +#define EXPORT __attribute__((visibility("default"))) + +/** + * Ensure the symbol is not exported + */ +#define PRIVATE __attribute__((visibility("hidden"))) + +/** + * For private fallback symbol definition + */ +#define WEAKFUNC __attribute__((weak)) + +/** + * Remove padding between fields + */ +#define PACKED __attribute__((packed)) + +/** + * Deprecate the interface + */ +#define DEPRECATE(x) __attribute__((deprecated(x))) + +/** + * Non-null argument + */ +#define NONNULL __attribute__((nonnull)) + +/* + * Find Last Set bit + */ +static inline int myfls(int mask) +{ +#if __has_builtin(__builtin_fls) + return __builtin_fls(mask); +#elif __has_builtin(__builtin_clz) + if(mask == 0) + return (0); + + return (sizeof(mask) << 3) - __builtin_clz(mask); +#else + int bit; + + if(mask == 0) + return (0); + for(bit = 1; mask != 1; bit++) + mask = (unsigned)mask >> 1; + return (bit); +#endif +} + +#endif /* Utilities_hpp */ diff --git a/RealtekPCIeCardReader/__DecodeCSD.cpp b/RealtekPCIeCardReader/__DecodeCSD.cpp new file mode 100644 index 0000000..0379d17 --- /dev/null +++ b/RealtekPCIeCardReader/__DecodeCSD.cpp @@ -0,0 +1,145 @@ +/// +/// Decode the given raw card specific data +/// +/// @param rcsd The raw card specific data +/// @return `true` on success, `false` otherwise. +/// @note Port: This function replaces `mmc_decode_csd()` defined in `sd.c`. +/// +bool IOSDCard::decodeCardSpecificData(CSDVX& rcsd) +{ + /// Data access time (TAAC): Time unit in nanoseconds + static constexpr UInt32 kTAACTimeUnits[] = + { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, + }; + + /// Data access time (TAAC): Time value multipliers (scaled to 10x) + static constexpr UInt32 kTAACTimeValues[] = + { + 00, 10, 12, 13, 15, 20, 25, 30, // 1.0 - 3.0x + 35, 40, 45, 50, 55, 60, 70, 80, // 3.5 - 8.0x + }; + + /// Maximum data transfer rate (DTR): Rate unit (scaled to 1/10x) + static constexpr UInt32 kDTRUnits[] = + { + 10000, // 100 Kbps + 100000, // 1 Mbps + 1000000, // 10 Mbps + 10000000, // 100 Mbps + 0, 0, 0, 0, + }; + + /// Maximum data transfer rate (DTR): Rate multiplier (scaled to 10x) + static constexpr UInt32 kDTRValues[] = + { + 00, 10, 12, 13, 15, 20, 25, 30, // 1.0 - 3.0x + 35, 40, 45, 50, 55, 60, 70, 80, // 3.5 - 8.0x + }; + + UInt32 version = rcsd.data[0] >> 6; + + switch (version) + { + // SDSC cards + case CSD::Version::k1: + { + this->isBlockAddressed = false; + + this->hasExtendedCapacity = false; + + this->csd.taacTimeNanosecs = (kTAACTimeUnits[rcsd.v1.taacTimeUnit] * kTAACTimeValues[rcsd.v1.taacTimeValue] + 9) / 10; + + this->csd.taacTimeClocks = rcsd.v1.nasc * 100; + + this->csd.maxDataTransferRate = kDTRUnits[rcsd.v1.maxTransferRateUnit] * kDTRValues[rcsd.v1.maxTransferRateValue]; + + this->csd.cardCommandClasses = rcsd.v1.cardCommandClasses; + + this->csd.capacity = (1 + rcsd.v1.deviceSize) << (rcsd.v1.deviceSizeMultiplier + 2); + + this->csd.readBlockLength = rcsd.v1.maxReadDataBlockLength; + + this->csd.canReadPartialBlock = rcsd.v1.isPartialBlockReadAllowed; + + this->csd.canWriteMisalignedBlock = rcsd.v1.writeBlockMisalignment; + + this->csd.canReadMisalignedBlock = rcsd.v1.readBlockMisalignment; + + this->csd.isDSRImplemented = rcsd.v1.isDSRImplemented; + + this->csd.writeSpeedFactor = rcsd.v1.writeSpeedFactor; + + this->csd.writeBlockLength = rcsd.v1.maxWriteDataBlockLength; + + this->csd.canWritePartialBlock = rcsd.v1.isPartialBlockWriteAllowed; + + if (rcsd.v1.isEraseSingleBlockEnabled) + { + this->csd.eraseSize = 1; + } + else if (this->csd.writeBlockLength >= 9) + { + this->csd.eraseSize = rcsd.v1.eraseSectorSize + 1; + + this->csd.eraseSize <<= (this->csd.writeBlockLength - 9); + } + + break; + } + + // SDHC / SDXC cards + case CSD::Version::k2: + { + this->isBlockAddressed = true; + + this->csd.taacTimeNanosecs = 0; + + this->csd.taacTimeClocks = 0; + + this->csd.maxDataTransferRate = kDTRUnits[rcsd.v2.maxTransferRateUnit] * kDTRValues[rcsd.v2.maxTransferRateValue]; + + this->csd.cardCommandClasses = rcsd.v2.cardCommandClasses; + + this->csd.deviceSize = rcsd.v2.deviceSize; + + this->hasExtendedCapacity = this->csd.deviceSize >= 0xFFFF; + + this->csd.capacity = (1 + this->csd.deviceSize) << 10; + + this->csd.readBlockLength = 9; + + this->csd.canReadPartialBlock = false; + + this->csd.canWriteMisalignedBlock = false; + + this->csd.canReadMisalignedBlock = false; + + this->csd.writeSpeedFactor = 4; // Unused + + this->csd.writeBlockLength = 9; + + this->csd.canWritePartialBlock = false; + + this->csd.eraseSize = 1; + + break; + } + + case CSD::Version::k3: + { + perr("SDUC card is not supported at this moment."); + + return false; + } + + default: + { + perr("Unrecognized CSD structure version %d.", version); + + return false; + } + } + + return true; +} \ No newline at end of file