2#include "VirtioScsiController.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/utility.h"
17#include "modules/drivers/common/InterruptProbe.h"
20constexpr size_t MaxCdbBytes = 32;
21constexpr size_t MaxSenseBytes = 96;
22constexpr size_t ResponseOffset = 128;
24struct VirtioScsiRequest {
30 uint8_t cdb[MaxCdbBytes];
32static_assert(offsetof(VirtioScsiRequest, cdb) == 19);
34struct VirtioScsiResponse {
37 uint16_t statusQualifier;
40 uint8_t sense[MaxSenseBytes];
42static_assert(offsetof(VirtioScsiResponse, sense) == 12);
43static_assert(ResponseOffset >=
sizeof(VirtioScsiRequest));
45constexpr size_t ResponseHeaderBytes = offsetof(VirtioScsiResponse, sense);
48VirtioScsiController::VirtioScsiController(
Device* pci)
51 m_Control(
"Virtio SCSI request"),
52 m_Data(
"Virtio SCSI data"),
53 m_Completion(0, false),
60 m_InterruptCompletions(0),
61 m_TransportInitialised(false),
64 m_CommandValid(false),
68 setSpecificType(
String(
"virtio-scsi-controller"));
71VirtioScsiController::~VirtioScsiController() {
75bool VirtioScsiController::initialiseController() {
78 !m_Transport.initialise()) {
81 m_TransportInitialised =
true;
82 if (!m_Transport.negotiate(0) || !m_Transport.setupQueue(2, m_RequestQueue) ||
83 m_RequestQueue.depth() < 3) {
87 uint32_t cdbBytes = 0, senseBytes = 0;
88 uint16_t maxTarget = 0;
89 if (!m_Transport.readDeviceConfig32(24, cdbBytes) ||
90 !m_Transport.readDeviceConfig32(20, senseBytes) ||
91 !m_Transport.readDeviceConfig16(30, maxTarget) || cdbBytes < 16 || cdbBytes > MaxCdbBytes ||
92 !senseBytes || senseBytes > MaxSenseBytes) {
93 ERROR(
"Virtio SCSI: unsupported command or sense size");
96 m_CdbBytes = cdbBytes;
97 m_SenseBytes = senseBytes;
108 m_Irq = m_Transport.registerInterrupt(
this);
110 ERROR(
"Virtio SCSI: could not register PCI interrupt");
113 if (!m_Transport.ready()) {
118 uint8_t inquiry[36]{};
119 const uint8_t inquiryCdb[6] = {0x12, 0, 0, 0,
sizeof(inquiry), 0};
120 if (!InterruptProbe::run(
122 return command(0, inquiryCdb,
sizeof(inquiryCdb), inquiry,
sizeof(inquiry),
false,
123 true) != Result::TransportError;
127 return m_InterruptCompletions;
129 ERROR(
"Virtio SCSI: interrupt delivery probe failed");
135 const unsigned lastTarget = maxTarget > 255 ? 255 : maxTarget;
136 for (
unsigned target = 0; target <= lastTarget; ++target) {
137 if (command(
static_cast<uint8_t
>(target), inquiryCdb,
sizeof(inquiryCdb), inquiry,
138 sizeof(inquiry),
false,
false) == Result::Success) {
139 m_Targets[m_UnitCount++] =
static_cast<uint8_t
>(target);
153void VirtioScsiController::drainCompletions(
bool fromInterrupt) {
159 while (m_RequestQueue.pop(completion)) {
160 m_CommandValid = completion.cookie ==
this && completion.length >= ResponseHeaderBytes;
161 m_CommandUsed = completion.length;
162 m_CommandSeen =
true;
164 ++m_InterruptCompletions;
175 const uint8_t isr = m_Transport.readIsr();
177 return IrqDisposition::NotHandled;
181 while (m_RequestQueue.pop(completion)) {
182 m_CommandValid = completion.cookie ==
this && completion.length >= ResponseHeaderBytes;
183 m_CommandUsed = completion.length;
184 m_CommandSeen =
true;
185 ++m_InterruptCompletions;
189 return IrqDisposition::Handled;
192void VirtioScsiController::failController() {
201 if (!m_Transport.reset()) {
202 panic(
"Virtio SCSI: device did not stop DMA after command failure");
205 m_RequestQueue.stop();
208VirtioScsiController::Result VirtioScsiController::command(uint8_t target,
const void* cdb,
209 size_t cdbBytes,
void* data,
210 size_t dataBytes,
bool writing,
211 bool interruptProbe) {
214 if (!m_Ready || m_Failed || !cdb || !cdbBytes || cdbBytes > m_CdbBytes ||
216 return Result::TransportError;
219 auto* request =
static_cast<VirtioScsiRequest*
>(m_Control.
virtualAddress());
220 auto* response =
reinterpret_cast<VirtioScsiResponse*
>(
221 static_cast<uint8_t*
>(m_Control.
virtualAddress()) + ResponseOffset);
222 ByteSet(request, 0,
sizeof(VirtioScsiRequest));
223 ByteSet(response, 0,
sizeof(VirtioScsiResponse));
225 request->lun[1] = target;
226 request->lun[2] = 0x40;
227 request->id = HOST_TO_LITTLE64(1);
228 MemoryCopy(request->cdb, cdb, cdbBytes);
229 if (writing && dataBytes) {
233 const uint32_t responseBytes = ResponseHeaderBytes + m_SenseBytes;
236 static_cast<uint32_t
>(offsetof(VirtioScsiRequest, cdb) + m_CdbBytes),
false},
238 {m_Data.
physicalAddress(),
static_cast<uint32_t
>(dataBytes), !writing},
240 size_t descriptorCount = dataBytes ? 3 : 2;
241 if (writing && dataBytes) {
242 descriptors[1] = descriptors[2];
243 descriptors[1].deviceWrites =
false;
244 descriptors[2] = {m_Control.
physicalAddress() + ResponseOffset, responseBytes,
true};
249 m_CommandSeen =
false;
250 m_CommandValid =
false;
252 [[maybe_unused]]
const size_t stale = m_Completion.
drainAvailable();
255 if (!m_RequestQueue.submit(descriptors, descriptorCount,
this)) {
257 return Result::TransportError;
259 m_Transport.notify(2);
261 const auto deadline = Time::getTicks() + 30 * Time::Multiplier::Second;
264 const bool grace = first && interruptProbe;
268 drainCompletions(
false);
270 bool seen =
false, valid =
false;
274 seen = m_CommandSeen;
275 valid = m_CommandValid;
276 used = m_CommandUsed;
281 ERROR(
"Virtio SCSI: malformed completion");
283 return Result::TransportError;
285 if (response->response || response->status || LITTLE_TO_HOST32(response->residual) ||
286 LITTLE_TO_HOST32(response->senseLength) > m_SenseBytes) {
287 return Result::DeviceError;
289 if (!writing && dataBytes) {
290 if (used < ResponseHeaderBytes + dataBytes) {
291 ERROR(
"Virtio SCSI: short read completion");
293 return Result::TransportError;
297 return Result::Success;
299 if (Time::getTicks() >= deadline) {
300 ERROR(
"Virtio SCSI: command timeout");
302 return Result::TransportError;
307bool VirtioScsiController::sendCommand(
size_t unit, uintptr_t cdb, uint8_t cdbBytes, uintptr_t data,
308 uint16_t dataBytes,
bool writing) {
309 if (unit >= m_UnitCount) {
312 return command(m_Targets[unit],
reinterpret_cast<const void*
>(cdb), cdbBytes,
313 reinterpret_cast<void*
>(data), dataBytes, writing,
false) == Result::Success;
316void VirtioScsiController::shutdown() {
328 if (m_TransportInitialised && !m_Transport.reset()) {
329 panic(
"Virtio SCSI: device did not stop DMA during shutdown");
332 m_RequestQueue.stop();
334 if (m_Irq && !Machine::instance().getIrqManager()->unregisterHandler(m_Irq,
this)) {
335 panic(
"Virtio SCSI: synchronous interrupt retirement failed");
uint16_t getPciDeviceId()
uint16_t getPciVendorId()
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