The Pedigree Project 0.1
AhciDisk.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
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
17#include "AhciDisk.h"
18#include "pedigree/kernel/Log.h"
19#include "pedigree/kernel/TargetInfo.h"
20#include "pedigree/kernel/machine/Pci.h"
21#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
22#include "pedigree/kernel/utilities/utility.h"
23
24#include "AhciController.h"
25
26AhciDisk::AhciDisk(AhciController* controller, size_t port)
27 : ScsiDisk(controller && PciBus::instance().hasDmaRemapping(controller->pciDevice())
28 ? 0
29 : PhysicalMemoryManager::below4GB),
30 m_Controller(controller),
31 m_Port(port),
32 m_Sectors(0),
33 m_SectorBytes(0),
34 m_Bytes(0),
35 m_ExtendedFlush(false),
36 m_Initialised(false),
37 m_Model{} {
38 m_pParent = controller;
39 setSpecificType(String("ahci-disk"));
40}
41
42Device* AhciDisk::dmaDevice() const {
43 return m_Controller ? m_Controller->pciDevice() : nullptr;
44}
45
46AhciDisk::~AhciDisk() {
47 // Cache callbacks dispatch virtually into this object and need its controller.
50}
51
52bool AhciDisk::initialise() {
53 if (m_Initialised)
54 return true;
55 uint16_t words[256] = {};
56 if (!m_Controller || !m_Controller->identify(m_Port, words))
57 return false;
58
59 const auto* raw = reinterpret_cast<const uint8_t*>(words);
60 if (raw[510] == 0xa5) {
61 uint8_t checksum = 0;
62 for (size_t i = 0; i < sizeof(words); ++i)
63 checksum += raw[i];
64 if (checksum) {
65 WARNING("AHCI: IDENTIFY checksum failed on port " << m_Port);
66 return false;
67 }
68 }
69 for (size_t i = 0; i < 256; ++i)
70 words[i] = LITTLE_TO_HOST16(words[i]);
71
72 if ((words[0] & 0x8000) || (words[49] & 0x0300) != 0x0300 || (words[83] & 0xc000) != 0x4000 ||
73 !(words[83] & (1U << 10))) {
74 WARNING("AHCI: port " << m_Port << " requires an ATA disk with DMA and LBA48");
75 return false;
76 }
77
78 uint64_t sectorBytes = 512;
79 if ((words[106] & 0xc000) == 0x4000 && (words[106] & (1U << 12))) {
80 const uint32_t sectorWords =
81 static_cast<uint32_t>(words[117]) | (static_cast<uint32_t>(words[118]) << 16);
82 if (sectorWords <= 256) {
83 WARNING("AHCI: invalid logical sector size on port " << m_Port);
84 return false;
85 }
86 sectorBytes = static_cast<uint64_t>(sectorWords) * 2;
87 }
88 if (sectorBytes < 512 || (sectorBytes & (sectorBytes - 1))) {
89 WARNING("AHCI: port " << m_Port << " has unsupported logical sector size " << sectorBytes);
90 return false;
91 }
92 const size_t pageBytes = TargetInfo::getPageSize();
93 if (!pageBytes || pageBytes > 65536 || (pageBytes % sectorBytes))
94 return false;
95
96 const uint64_t sectors =
97 static_cast<uint64_t>(words[100]) | (static_cast<uint64_t>(words[101]) << 16) |
98 (static_cast<uint64_t>(words[102]) << 32) | (static_cast<uint64_t>(words[103]) << 48);
99 if (!sectors || sectors > (uint64_t{1} << 48) || sectors > (~size_t{0} / sectorBytes)) {
100 WARNING("AHCI: invalid or unaddressable LBA48 capacity on port " << m_Port);
101 return false;
102 }
103 const bool supportsFlush = words[83] & (1U << 12);
104 m_ExtendedFlush = words[83] & (1U << 13);
105 if (!supportsFlush && !m_ExtendedFlush) {
106 WARNING("AHCI: port " << m_Port << " lacks a supported cache flush command");
107 return false;
108 }
109
110 for (size_t i = 0; i < 40; ++i) {
111 const uint8_t ch = words[27 + i / 2] >> ((i & 1) ? 0 : 8);
112 m_Model[i] = ch >= 32 && ch <= 126 ? static_cast<char>(ch) : ' ';
113 }
114 size_t length = 40;
115 while (length && m_Model[length - 1] == ' ')
116 --length;
117 m_Model[length] = 0;
118
119 const bool cacheSupported = words[82] & (1U << 5);
120 const bool enabledValid = (words[87] & 0xc000) == 0x4000;
121 const bool cacheEnabled = words[85] & (1U << 5);
122 const char* cacheState = !cacheSupported ? "unsupported"
123 : !enabledValid ? "unknown"
124 : cacheEnabled ? "enabled"
125 : "disabled";
126
127 m_SectorBytes = static_cast<size_t>(sectorBytes);
128 m_Controller->configureDisk(
129 m_Port, m_SectorBytes,
130 words[76] != 0xffff && (words[76] & (1U << 8)) ? (words[75] & 31U) + 1 : 0);
131 m_Sectors = static_cast<size_t>(sectors);
132 m_Bytes = m_Sectors * sectorBytes;
133 m_Initialised = true;
134 NOTICE("AHCI: disk port " << m_Port << " model '" << m_Model << "', " << Dec << m_Sectors
135 << " sectors of " << m_SectorBytes << " bytes, " << m_Bytes
136 << " bytes; write cache " << cacheState << ", flush "
137 << (m_ExtendedFlush ? "EXT" : "legacy") << Hex);
138 return true;
139}
140
142 name.assign(m_Model[0] ? m_Model : "AHCI Disk");
143}
144
145size_t AhciDisk::getSize() const {
146 return m_Bytes;
147}
148
149size_t AhciDisk::getBlockCount() const {
150 return m_Sectors;
151}
152
155}
156
158 return m_SectorBytes;
159}
160
161size_t AhciDisk::validPageLength(uint64_t location) const {
162 if (!m_Initialised || location >= m_Bytes || (location % m_SectorBytes))
163 return 0;
164 const uint64_t remaining = m_Bytes - location;
165 const size_t pageBytes = TargetInfo::getPageSize();
166 return remaining < pageBytes ? static_cast<size_t>(remaining) : pageBytes;
167}
168
169bool AhciDisk::transferReadBuffers(Disk::ReadBuffer* buffers, size_t count) {
170 if (count > Disk::MaxReadBuffers || (count && !buffers))
171 return false;
172 for (size_t i = 0; i < count; ++i)
173 buffers[i].complete = false;
174 if (!count)
175 return true;
176 if (!m_Initialised)
177 return false;
178 for (size_t i = 0; i < count; ++i) {
179 if (!buffers[i].buffer || !buffers[i].length || buffers[i].length > TargetInfo::getPageSize() ||
180 buffers[i].location >= m_Bytes || buffers[i].length > m_Bytes - buffers[i].location ||
181 buffers[i].location % m_SectorBytes || buffers[i].length % m_SectorBytes)
182 return false;
183 }
184 return m_Controller->readBatch(m_Port, buffers, count);
185}
186
187bool AhciDisk::transferWriteBuffers(Disk::WriteBuffer* buffers, size_t count) {
188#if CRIPPLE_HDD
189 return false;
190#else
191 return m_Initialised && m_Controller->writeBatch(m_Port, buffers, count);
192#endif
193}
194
195uint64_t AhciDisk::doRead(uint64_t location) {
196 const size_t bytes = getCacheFillLength(location);
197 if (!m_Initialised || !bytes)
198 return 0;
199
200 const uintptr_t existing = getCache().lookup(location);
201 if (existing) {
202 getCache().release(location);
203 return bytes;
204 }
205 bool existed = false;
206 const uintptr_t page = getCache().insert(location, &existed);
207 if (!page)
208 return 0;
209 if (existed)
210 return bytes;
211
212 // Terminal sectors may occupy only part of a cache page.
213 ByteSet(reinterpret_cast<void*>(page), 0, TargetInfo::getPageSize());
214 if (!m_Controller->readWrite(m_Port, location / m_SectorBytes,
215 static_cast<uint16_t>(bytes / m_SectorBytes),
216 reinterpret_cast<void*>(page), bytes, false, true)) {
217 if (!getCache().discardEditing(location))
218 FATAL("AHCI: failed to discard an incomplete cache fill");
219 return 0;
220 }
221 getCache().markNoLongerEditing(location);
222 return bytes;
223}
224
225uint64_t AhciDisk::doWrite(uint64_t location) {
226#if CRIPPLE_HDD
227 return 0;
228#else
229 if (!validPageLength(location))
230 return 0;
231 const uintptr_t page = getCache().lookup(location);
232 if (!page)
233 return 0;
234 // The controller separately releases the reference transferred by write().
235 CachePageGuard guard(getCache(), location);
236 return doWriteDirect(location, page);
237#endif
238}
239
240bool AhciDisk::transferBuffer(uint64_t location, void* buffer, size_t length, bool writing) {
241#if CRIPPLE_HDD
242 if (writing)
243 return false;
244#endif
245 if (!m_Initialised || !buffer || !length || location >= m_Bytes || length > m_Bytes - location ||
246 location % m_SectorBytes || length % m_SectorBytes || length > TargetInfo::getPageSize() ||
247 length / m_SectorBytes > 0xffff)
248 return false;
249 return m_Controller->readWrite(m_Port, location / m_SectorBytes,
250 static_cast<uint16_t>(length / m_SectorBytes), buffer, length,
251 writing);
252}
253
254uint64_t AhciDisk::doWriteDirect(uint64_t location, uintptr_t page) {
255#if CRIPPLE_HDD
256 return 0;
257#else
258 const size_t bytes = validPageLength(location);
259 if (!page || !bytes)
260 return 0;
261 return m_Controller->readWrite(m_Port, location / m_SectorBytes,
262 static_cast<uint16_t>(bytes / m_SectorBytes),
263 reinterpret_cast<void*>(page), bytes, true)
264 ? bytes
265 : 0;
266#endif
267}
268
269uint64_t AhciDisk::doWriteDirectPhysical(uint64_t location, uintptr_t page,
270 physical_uintptr_t physical) {
271#if CRIPPLE_HDD
272 return 0;
273#else
274 const size_t bytes = validPageLength(location);
275 if (!page || !physical || !bytes) {
276 return 0;
277 }
278 WriteBuffer buffer = {location, reinterpret_cast<const void*>(page), bytes, false, physical};
279 return transferWriteBuffers(&buffer, 1) && buffer.complete ? bytes : 0;
280#endif
281}
282
283uint64_t AhciDisk::doSync(uint64_t location) {
284#if CRIPPLE_HDD
285 return 0;
286#else
287 const size_t bytes =
288 location == SyncWholeDevice ? (m_Initialised ? 1 : 0) : validPageLength(location);
289 if (!bytes)
290 return 0;
291 return m_Controller->flush(m_Port, m_ExtendedFlush) ? bytes : 0;
292#endif
293}
size_t getNativeBlockSize() const override
Definition AhciDisk.cc:157
size_t getBlockSize() const override
Gets the preferred I/O extent of the disk.
Definition AhciDisk.cc:153
size_t getSize() const override
Gets the size of the disk.
Definition AhciDisk.cc:145
uint64_t doWriteDirect(uint64_t location, uintptr_t page) override
Definition AhciDisk.cc:254
void getName(String &name) override
Definition AhciDisk.cc:141
void release(uintptr_t key)
Definition Cache.cc:1570
uintptr_t insert(uintptr_t key, bool *alreadyExisted=nullptr)
Definition Cache.cc:890
void markNoLongerEditing(uintptr_t key, size_t length=0)
Definition Cache.cc:2646
uintptr_t lookup(uintptr_t key)
Definition Cache.cc:737
void retireEndpoint()
Definition Disk.cc:133
Definition Pci.h:31
size_t getCacheFillLength(uint64_t location) const
Definition ScsiDisk.cc:1389
void shutdownCache(bool deviceAvailable=true)
Definition ScsiDisk.cc:268
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124