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