2#include "VirtioBlkController.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/TargetInfo.h"
6#include "pedigree/kernel/machine/IrqManager.h"
7#include "pedigree/kernel/machine/Machine.h"
8#include "pedigree/kernel/panic.h"
9#include "pedigree/kernel/process/TerminationDeferral.h"
10#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
11#include "pedigree/kernel/processor/VirtualAddressSpace.h"
12#include "pedigree/kernel/time/Time.h"
13#include "pedigree/kernel/utilities/new"
14#include "pedigree/kernel/utilities/utility.h"
18#include "VirtioBlkDisk.h"
19#include "modules/drivers/common/InterruptProbe.h"
22constexpr uint64_t FeatureReadOnly = uint64_t{1} << 5;
23constexpr uint64_t FeatureBlockSize = uint64_t{1} << 6;
24constexpr uint64_t FeatureFlush = uint64_t{1} << 9;
25constexpr uint32_t RequestRead = 0;
26constexpr uint32_t RequestWrite = 1;
27constexpr uint32_t RequestFlush = 4;
29struct VirtioBlkRequest {
35static_assert(offsetof(VirtioBlkRequest, status) == 16);
38VirtioBlkController::VirtioBlkController(
Device* pci)
41 m_Control(
"Virtio block request"),
42 m_Data(
"Virtio block data"),
43 m_Completion(0, false),
48 m_InterruptCompletions(0),
51 m_TransportInitialised(false),
54 m_CommandValid(false),
58 setSpecificType(
String(
"virtio-blk-controller"));
61VirtioBlkController::~VirtioBlkController() {
65bool VirtioBlkController::initialiseController() {
68 !m_Transport.initialise()) {
71 m_TransportInitialised =
true;
72 if (!m_Transport.negotiate(FeatureReadOnly | FeatureBlockSize | FeatureFlush) ||
73 !m_Transport.setupQueue(0, m_Queue) || m_Queue.depth() < 3) {
77 const uint64_t features = m_Transport.features();
78 m_ReadOnly = features & FeatureReadOnly;
79 m_Flush = features & FeatureFlush;
81 if (!m_Transport.readDeviceConfig64(0, sectors) || !sectors ||
82 sectors >
static_cast<uint64_t
>(~
size_t{0} / 512)) {
85 m_Bytes =
static_cast<size_t>(sectors * 512);
86 if (features & FeatureBlockSize) {
87 uint32_t blockSize = 0;
88 if (!m_Transport.readDeviceConfig32(20, blockSize)) {
91 m_SectorBytes = blockSize;
94 if (m_SectorBytes < 512 || (m_SectorBytes & (m_SectorBytes - 1)) || m_SectorBytes > page ||
95 page % m_SectorBytes || m_Bytes % m_SectorBytes) {
108 m_Irq = m_Transport.registerInterrupt(
this);
110 ERROR(
"Virtio block: could not register PCI interrupt");
113 if (!m_Transport.ready()) {
118 if (!InterruptProbe::run([&] {
return command(RequestRead, 0,
nullptr, m_SectorBytes, 2,
true); },
121 return m_InterruptCompletions;
123 ERROR(
"Virtio block: interrupt delivery probe failed");
129 disk->publishEndpoint();
130 NOTICE(
"Virtio block: " <<
Dec << sectors <<
" sectors, " << m_Bytes <<
" bytes, "
131 << m_SectorBytes <<
"-byte blocks, "
132 << (m_ReadOnly ?
"read-only" :
"read-write") <<
Hex);
136void VirtioBlkController::drainCompletions(
bool fromInterrupt) {
142 while (m_Queue.pop(completion)) {
143 m_CommandValid = completion.cookie ==
this && completion.length >= m_ExpectedUsed;
144 m_CommandSeen =
true;
146 ++m_InterruptCompletions;
157 const uint8_t isr = m_Transport.readIsr();
159 return IrqDisposition::NotHandled;
163 while (m_Queue.pop(completion)) {
164 m_CommandValid = completion.cookie ==
this && completion.length >= m_ExpectedUsed;
165 m_CommandSeen =
true;
166 ++m_InterruptCompletions;
170 return IrqDisposition::Handled;
173void VirtioBlkController::failController() {
182 if (!m_Transport.reset()) {
183 panic(
"Virtio block: device did not stop DMA after command failure");
189bool VirtioBlkController::command(uint32_t type, uint64_t sector,
void* buffer,
size_t bytes,
190 size_t timeoutSeconds,
bool interruptProbe) {
194 (type == RequestWrite && (!buffer || m_ReadOnly)) ||
195 (type == RequestFlush && (bytes || buffer)) || (type != RequestFlush && !bytes)) {
199 auto* request =
static_cast<VirtioBlkRequest*
>(m_Control.
virtualAddress());
200 request->type = HOST_TO_LITTLE32(type);
201 request->reserved = 0;
202 request->sector = HOST_TO_LITTLE64(sector);
203 request->status = 0xff;
204 if (type == RequestWrite) {
210 {m_Data.
physicalAddress(),
static_cast<uint32_t
>(bytes), type == RequestRead},
213 const size_t descriptorCount = type == RequestFlush ? 2 : 3;
214 if (type == RequestFlush) {
215 descriptors[1] = descriptors[2];
219 m_CommandSeen =
false;
220 m_CommandValid =
false;
221 m_ExpectedUsed = type == RequestRead ? bytes + 1 : 1;
222 [[maybe_unused]]
const size_t stale = m_Completion.
drainAvailable();
225 if (!m_Queue.submit(descriptors, descriptorCount,
this)) {
229 m_Transport.notify(0);
231 const auto deadline = Time::getTicks() + timeoutSeconds * Time::Multiplier::Second;
234 const bool grace = first && interruptProbe;
238 drainCompletions(
false);
244 seen = m_CommandSeen;
245 valid = m_CommandValid;
250 ERROR(
"Virtio block: malformed completion");
254 if (request->status != 0) {
255 WARNING(
"Virtio block: request " << type <<
" failed with status " << request->status);
258 if (type == RequestRead && buffer) {
263 if (Time::getTicks() >= deadline) {
264 ERROR(
"Virtio block: command timeout");
271bool VirtioBlkController::readWrite(uint64_t location,
void* buffer,
size_t bytes,
bool writing) {
273 bytes > m_Bytes - location || location % m_SectorBytes || bytes % m_SectorBytes ||
274 (writing && m_ReadOnly)) {
277 return command(writing ? RequestWrite : RequestRead, location / 512, buffer, bytes, 30, false);
280bool VirtioBlkController::flush() {
283 return (m_ReadOnly || !m_Flush) ? m_Ready : command(RequestFlush, 0,
nullptr, 0, 30,
false);
286void VirtioBlkController::shutdown() {
298 if (m_TransportInitialised && !m_Transport.reset()) {
299 panic(
"Virtio block: device did not stop DMA during shutdown");
304 if (m_Irq && !Machine::instance().getIrqManager()->unregisterHandler(m_Irq,
this)) {
305 panic(
"Virtio block: synchronous interrupt retirement failed");
uint16_t getPciDeviceId()
uint16_t getPciVendorId()
void addChild(Device *pDevice)
void * virtualAddress() const
physical_uintptr_t physicalAddress() const
static const size_t continuous
static PhysicalMemoryManager & instance()
void shutdownDiskCaches()
MUST_USE_RESULT size_t drainAvailable()
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
static constexpr size_t getPageSize() noexcept
IrqDisposition irq(irq_id_t number) override
static const size_t KernelMode
static const size_t Write
void EXPORTED_PUBLIC panic(const char *msg) NORETURN