The Pedigree Project 0.1
AhciController.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 * SPDX-License-Identifier: ISC
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16#include "AhciController.h"
17#include "pedigree/kernel/LockGuard.h"
18#include "pedigree/kernel/Log.h"
19#include "pedigree/kernel/machine/IrqManager.h"
20#include "pedigree/kernel/machine/Machine.h"
21#include "pedigree/kernel/machine/Pci.h"
22#include "pedigree/kernel/panic.h"
23#include "pedigree/kernel/processor/IoBase.h"
24#include "pedigree/kernel/time/Time.h"
25#include "pedigree/kernel/utilities/new"
26
27#include "AhciDisk.h"
28#include "AhciPort.h"
29#include "Registers.h"
30#include "modules/drivers/common/InterruptProbe.h"
31
32using namespace Ahci;
33
34AhciController::AhciController(Device* pci)
36 m_Pci(pci),
37 m_Registers(nullptr),
38 m_Ports{},
39 m_Irq(0),
40 m_Implemented(0),
41 m_OriginalCommand(0),
42 m_PciChanged(false),
43 m_HardwareOwned(false),
44 m_Interrupts(false),
45 m_Stopping(false),
46 m_Shutdown(false) {
47 setSpecificType(String("ahci-controller"));
48}
49AhciController::~AhciController() {
50 shutdown();
51 for (size_t i = 0; i < 32; ++i)
52 delete m_Ports[i];
53}
54bool AhciController::claimOwnership(uint32_t version) {
55 if (version < 0x00010200 || !(m_Registers->read32(Cap2) & 1U))
56 return true;
57 uint32_t ownership = m_Registers->read32(Bohc);
58 m_Registers->write32((ownership & ~(1U << 3)) | (1U << 1), Bohc);
59 const auto start = Time::getTicks();
60 bool busy = (ownership & (1U << 4)) != 0;
61 do {
62 ownership = m_Registers->read32(Bohc);
63 busy |= (ownership & (1U << 4)) != 0;
64 if (!(ownership & ((1U << 4) | 1U)))
65 return true;
66 Time::delay(Time::Multiplier::Millisecond);
67 } while (Time::getTicks() - start < 25 * Time::Multiplier::Millisecond);
68 // AHCI 10.6.3 permits takeover after 25 ms when firmware never declared busy.
69 if (!busy)
70 return true;
71 const auto deadline = start + 2 * Time::Multiplier::Second + 25 * Time::Multiplier::Millisecond;
72 do {
73 ownership = m_Registers->read32(Bohc);
74 if (!(ownership & ((1U << 4) | 1U)))
75 return true;
76 Time::delay(Time::Multiplier::Millisecond);
77 } while (Time::getTicks() < deadline);
78 ERROR("AHCI: firmware did not release controller ownership");
79 return false;
80}
81bool AhciController::reset() {
82 m_Registers->write32(Enable, Ghc);
83 m_Registers->write32(Enable | Reset, Ghc);
84 const auto deadline = Time::getTicks() + Time::Multiplier::Second;
85 while (m_Registers->read32(Ghc) & Reset) {
86 if (Time::getTicks() >= deadline)
87 return false;
88 Time::delay(Time::Multiplier::Millisecond);
89 }
90 m_Registers->write32(Enable, Ghc);
91 return (m_Registers->read32(Ghc) & Enable) != 0;
92}
93bool AhciController::initialiseController() {
94 if (!m_Pci || m_Pci->getPciClassCode() != 1 || m_Pci->getPciSubclassCode() != 6 ||
95 m_Pci->getPciProgInterface() != 1)
96 return false;
97 auto& pci = PciBus::instance();
99 if (!pci.inspectFunction(m_Pci, inherited)) {
100 ERROR("AHCI: unsupported PCI state (D0, valid capabilities and PIC INTx required)");
101 return false;
102 }
103 m_OriginalCommand = inherited.command;
104 const uint32_t bar = pci.readConfigSpace(m_Pci, 9);
105 if ((bar & 7U) || !((bar & ~15U)))
106 return false;
107 Device::Address* mapping = nullptr;
108 for (auto* address : m_Pci->addresses()) {
109 if (address->m_Name == "bar5" && !address->m_IsIoSpace && address->m_Address == (bar & ~15U)) {
110 mapping = address;
111 break;
112 }
113 }
114 if (!mapping || mapping->m_Size < PortBase + PortStride)
115 return false;
116 // Keep the original PCI node responsible for its BAR mapping.
117 m_PciChanged = true;
118 if (!pci.updateCommand(m_Pci, 0, 2U | 0x400U))
119 return false;
120 mapping->map();
121 m_Registers = mapping->m_Io;
122 if (!m_Registers || m_Registers->size() < mapping->m_Size)
123 return false;
124 const uint32_t version = m_Registers->read32(Vs);
125 if (version < 0x00010000 || version == 0xffffffffU || !claimOwnership(version))
126 return false;
127 m_HardwareOwned = true;
128 m_Implemented = m_Registers->read32(Pi);
129 if (!m_Implemented)
130 return false;
131 uint32_t speedLimits[32] = {};
132 for (size_t i = 0; i < 32; ++i) {
133 if (!(m_Implemented & (1U << i)))
134 continue;
135 if (mapping->m_Size < PortBase + (i + 1) * PortStride)
136 return false;
137 speedLimits[i] = m_Registers->read32(PortBase + i * PortStride + Sctl) & 0xf0U;
138 }
139 if (!reset()) {
140 ERROR("AHCI: HBA reset did not complete within one second");
141 return false;
142 }
143 const uint32_t capabilities = m_Registers->read32(Cap);
144 const uint32_t extended = version >= 0x00010200 ? m_Registers->read32(Cap2) : 0;
145 if (capabilities & (1U << 7))
146 m_Registers->write32(0, CccCtl);
147 // DMA addresses are below 4 GiB even on controllers advertising S64A.
148 if (!pci.disableMessageInterrupts(m_Pci, inherited) ||
149 !pci.resourcesUnchanged(m_Pci, inherited) || !pci.updateCommand(m_Pci, 0, 6U | 0x400U))
150 return false;
151 for (size_t i = 0; i < 32; ++i) {
152 if (!(m_Implemented & (1U << i)))
153 continue;
154 m_Registers->write32(speedLimits[i], PortBase + i * PortStride + Sctl);
155 auto* port = new AhciPort(m_Registers, i);
156 m_Ports[i] = port;
157 if (!port->initialise(capabilities, version, extended)) {
158 delete port;
159 m_Ports[i] = nullptr;
160 continue;
161 }
162 auto* disk = new AhciDisk(this, i);
163 if (disk->initialise()) {
164 addChild(disk);
165 } else {
166 delete disk;
167 delete port;
168 m_Ports[i] = nullptr;
169 }
170 }
171 if (!getNumChildren())
172 return false;
173 m_Registers->write32(m_Implemented, Is);
174 m_Irq = Machine::instance().getIrqManager()->registerPciIrqHandler(this, m_Pci,
175 IrqPolicy::pciIntxThreaded());
176 if (!m_Irq) {
177 ERROR("AHCI: could not register PCI INTx");
178 return false;
179 }
180 {
181 LockGuard<Mutex> irqLock(m_IrqLock);
182 for (auto* port : m_Ports)
183 if (port)
184 port->enableInterrupts();
185 m_Registers->write32(m_Implemented, Is);
186 m_Interrupts = true;
187 if (!pci.updateCommand(m_Pci, 0x400U, 6U))
188 return false;
189 m_Registers->write32(Enable | InterruptEnable, Ghc);
190 (void)m_Registers->read32(Ghc);
191 }
192 uint16_t identifyData[256]{};
193 for (size_t i = 0; i < 32; ++i) {
194 if (!m_Ports[i])
195 continue;
196 if (!InterruptProbe::run([&] { return identify(i, identifyData, true); },
197 [&] { return m_Ports[i]->interruptCompletions(); })) {
198 ERROR("AHCI: interrupt delivery probe failed on port " << i);
199 return false;
200 }
201 }
202 for (size_t i = 0; i < getNumChildren(); ++i) {
203 auto* disk = static_cast<AhciDisk*>(getChild(i));
204 disk->publishEndpoint();
205 NOTICE("AHCI: SATA disk ready on port " << disk->port());
206 }
207 NOTICE("AHCI: controller ready with " << getNumChildren() << " SATA disks, shared INTx");
208 return true;
209}
211 LockGuard<Mutex> irqLock(m_IrqLock);
212 if (m_Stopping)
214 if (!m_Interrupts)
215 return IrqDisposition::NotHandled;
216 const uint32_t pending = m_Registers->read32(Is) & m_Implemented;
217 bool handled = pending != 0;
218 for (size_t i = 0; i < 32; ++i) {
219 if (m_Ports[i])
220 handled |= m_Ports[i]->interrupt(pending & (1U << i));
221 else if (pending & (1U << i)) {
222 const size_t base = PortBase + i * PortStride;
223 m_Registers->write32(m_Registers->read32(base + Serr), base + Serr);
224 m_Registers->write32(m_Registers->read32(base + PortIs), base + PortIs);
225 }
226 }
227 if (pending)
228 m_Registers->write32(pending, Is);
229 (void)m_Registers->read32(Is);
230 return handled ? IrqDisposition::Handled : IrqDisposition::NotHandled;
231}
232bool AhciController::identify(size_t port, uint16_t* words, bool interruptProbe) {
233 return port < 32 && m_Ports[port] &&
234 m_Ports[port]->command(0xec, 0, 0, words, 512, false, m_Interrupts, interruptProbe);
235}
236bool AhciController::readWrite(size_t port, uint64_t lba, uint16_t sectors, void* buffer,
237 size_t bytes, bool writing) {
238 if (port >= 32 || !m_Ports[port] || !sectors ||
239 bytes != static_cast<size_t>(sectors) * m_Ports[port]->sectorBytes() || lba >= (1ULL << 48) ||
240 sectors > (1ULL << 48) - lba)
241 return false;
242 return port < 32 && m_Ports[port] &&
243 m_Ports[port]->command(writing ? 0x35 : 0x25, lba, sectors, buffer, bytes, writing,
244 m_Interrupts);
245}
246void AhciController::configureDisk(size_t port, size_t sectorBytes, size_t queueDepth) {
247 if (port < 32 && m_Ports[port])
248 m_Ports[port]->configureDisk(sectorBytes, queueDepth);
249}
250bool AhciController::readBatch(size_t port, Disk::ReadBuffer* buffers, size_t count) {
251 return port < 32 && m_Ports[port] && m_Ports[port]->readBatch(buffers, count, m_Interrupts);
252}
253
254bool AhciController::writeBatch(size_t port, Disk::WriteBuffer* buffers, size_t count) {
255 return port < 32 && m_Ports[port] && m_Ports[port]->writeBatch(buffers, count, m_Interrupts);
256}
257
258bool AhciController::flush(size_t port, bool extended) {
259 return port < 32 && m_Ports[port] &&
260 m_Ports[port]->command(extended ? 0xea : 0xe7, 0, 0, nullptr, 0, false, m_Interrupts);
261}
262size_t AhciController::interruptCompletions() const {
263 size_t count = 0;
264 for (auto* port : m_Ports)
265 if (port)
266 count += port->interruptCompletions();
267 return count;
268}
269void AhciController::shutdown() {
270 if (m_Shutdown)
271 return;
272 // Cache writeback and queued requests still need both hardware and interrupts.
275 {
276 LockGuard<Mutex> irqLock(m_IrqLock);
277 m_Stopping = true;
278 m_Interrupts = false;
279 if (m_HardwareOwned) {
280 m_Registers->write32(Enable, Ghc);
281 (void)m_Registers->read32(Ghc);
282 }
283 }
284 if (m_Irq && !Machine::instance().getIrqManager()->unregisterHandler(m_Irq, this))
285 panic("AHCI: synchronous interrupt retirement failed");
286 m_Irq = 0;
287 for (auto* port : m_Ports)
288 if (port)
289 port->shutdown();
290 if (m_PciChanged) {
291 // Reset replaced firmware's command state. Never resume its old bus mastering.
292 const uint16_t command =
293 m_HardwareOwned ? (m_OriginalCommand & ~4U) | 0x400U : m_OriginalCommand;
294 if (!PciBus::instance().updateCommand(m_Pci, 0x407U, command & 0x407U))
295 panic("AHCI: failed to verify PCI state during shutdown");
296 }
297 m_Shutdown = true;
298}
299
300size_t AhciController::maximumOutstanding(size_t port) const {
301 return port < 32 && m_Ports[port] ? m_Ports[port]->maximumOutstanding() : 0;
302}
IrqDisposition irq(irq_id_t number) override
IoBase * m_Io
Definition Device.h:110
size_t m_Size
Definition Device.h:104
void map(size_t forcedSize=0, bool bUser=false, bool bWriteCombine=false, bool bWriteThrough=false)
Definition Device.cc:334
size_t getNumChildren()
Definition Device.cc:131
Device * getChild(size_t n)
Definition Device.cc:127
virtual Vector< Address * > & addresses()
Definition Device.h:249
uint8_t getPciClassCode()
Definition Device.h:211
void addChild(Device *pDevice)
Definition Device.cc:123
uint8_t getPciSubclassCode()
Definition Device.h:215
uint8_t getPciProgInterface()
Definition Device.h:227
virtual void write32(uint32_t value, size_t offset=0)=0
virtual uint32_t read32(size_t offset=0)=0
virtual size_t size() const =0
virtual irq_id_t registerPciIrqHandler(IrqHandler *handler, Device *pDevice, const IrqPolicy &policy)=0
virtual void destroy()
void shutdownDiskCaches()
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117
IrqDisposition
Definition IrqHandler.h:31