diff --git a/build/jam/packages/Haiku b/build/jam/packages/Haiku index 9d4753745e..b582105df4 100644 --- a/build/jam/packages/Haiku +++ b/build/jam/packages/Haiku @@ -53,6 +53,7 @@ AddFilesToPackage add-ons kernel busses random : ; AddFilesToPackage add-ons kernel busses scsi : ahci + hyperv_scsi@x86,x86_64 virtio_scsi ; AddFilesToPackage add-ons kernel busses usb : @@ -326,6 +327,7 @@ AddBootModuleSymlinksToPackage efi_gpt generic_ide_pci hyperv@x86,x86_64 + hyperv_scsi@x86,x86_64 ide_isa@x86 isa@x86,x86_64 intel diff --git a/build/jam/packages/HaikuBootstrap b/build/jam/packages/HaikuBootstrap index 009da6f8f1..46c7819021 100644 --- a/build/jam/packages/HaikuBootstrap +++ b/build/jam/packages/HaikuBootstrap @@ -26,6 +26,7 @@ AddFilesToPackage add-ons kernel busses pci : ; AddFilesToPackage add-ons kernel busses scsi : ahci + hyperv_scsi@x86,x86_64 virtio_scsi ; AddFilesToPackage add-ons kernel busses usb : @@ -323,6 +324,7 @@ AddBootModuleSymlinksToPackage virtio_block virtio_scsi hyperv@x86,x86_64 + hyperv_scsi@x86,x86_64 efi_gpt intel bfs diff --git a/headers/private/hyperv/hyperv.h b/headers/private/hyperv/hyperv.h index 68df780e18..02957b43dd 100644 --- a/headers/private/hyperv/hyperv.h +++ b/headers/private/hyperv/hyperv.h @@ -27,7 +27,7 @@ #define HYPERV_PRETTYNAME_FIBRECHANNEL "Hyper-V Fibre Channel" #define HYPERV_PRETTYNAME_FILECOPY "Hyper-V File Copy" #define HYPERV_PRETTYNAME_HEARTBEAT "Hyper-V Heartbeat" -#define HYPERV_PRETTYNAME_IDE "Hyper-V IDE Accelerator" +#define HYPERV_PRETTYNAME_IDE "Hyper-V IDE Controller" #define HYPERV_PRETTYNAME_INPUT "Hyper-V Input" #define HYPERV_PRETTYNAME_KEYBOARD "Hyper-V Keyboard" #define HYPERV_PRETTYNAME_KVP "Hyper-V Data Exchange" @@ -36,7 +36,7 @@ #define HYPERV_PRETTYNAME_RDCONTROL "Hyper-V Remote Desktop Control" #define HYPERV_PRETTYNAME_RDMA "Hyper-V RDMA" #define HYPERV_PRETTYNAME_RDVIRT "Hyper-V Remote Desktop Virtualization" -#define HYPERV_PRETTYNAME_SCSI "Hyper-V SCSI Adapter" +#define HYPERV_PRETTYNAME_SCSI "Hyper-V SCSI Controller" #define HYPERV_PRETTYNAME_SHUTDOWN "Hyper-V Guest Shutdown" #define HYPERV_PRETTYNAME_TIMESYNC "Hyper-V Time Synchronization" #define HYPERV_PRETTYNAME_VSS "Hyper-V Volume Shadow Copy" diff --git a/src/add-ons/kernel/busses/scsi/Jamfile b/src/add-ons/kernel/busses/scsi/Jamfile index 3b1f30f2e6..65e1b70414 100644 --- a/src/add-ons/kernel/busses/scsi/Jamfile +++ b/src/add-ons/kernel/busses/scsi/Jamfile @@ -3,5 +3,6 @@ SubDir HAIKU_TOP src add-ons kernel busses scsi ; SubInclude HAIKU_TOP src add-ons kernel busses scsi ahci ; SubInclude HAIKU_TOP src add-ons kernel busses scsi 53c8xx ; SubInclude HAIKU_TOP src add-ons kernel busses scsi buslogic ; +SubInclude HAIKU_TOP src add-ons kernel busses scsi hyperv ; SubInclude HAIKU_TOP src add-ons kernel busses scsi usb ; SubInclude HAIKU_TOP src add-ons kernel busses scsi virtio ; diff --git a/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSI.cpp b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSI.cpp new file mode 100644 index 0000000000..1a07c390ee --- /dev/null +++ b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSI.cpp @@ -0,0 +1,608 @@ +/* + * Copyright 2026 John Davis. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "HyperVSCSI.h" + + +HyperVSCSI::HyperVSCSI(device_node* node) + : + fNode(node), + fBus(NULL), + fBusDPC(NULL), + fStatus(B_NO_INIT), + fIsIDE(false), + fTargetID(0), + fMaxDevices(HV_SCSI_MAX_SCSI_DEVICES), + fVersion(0), + fMaxSubChannels(0), + fMaxTransferBytes(0), + fPacket(NULL), + fHyperV(NULL), + fHyperVCookie(NULL) +{ + CALLED(); + + mutex_init(&fRequestLock, "hyperv_scsi requests"); + + // VMBus device will be the parent of the parent + DeviceNodePutter<&gDeviceManager> simParent(gDeviceManager->get_parent_node(node)); + DeviceNodePutter<&gDeviceManager> parent(gDeviceManager->get_parent_node(simParent.Get())); + gDeviceManager->get_driver(parent.Get(), (driver_module_info**)&fHyperV, + (void**)&fHyperVCookie); + + // Check if parent is a Hyper-V IDE controller + const char* type; + fStatus = gDeviceManager->get_attr_string(parent.Get(), HYPERV_DEVICE_TYPE_STRING_ITEM, &type, + false); + if (fStatus != B_OK) + return; + + fIsIDE = strcmp(type, VMBUS_TYPE_IDE) == 0; + if (fIsIDE) { + const vmbus_guid_t* instanceID = NULL; + size_t instanceIDLength; + fStatus = gDeviceManager->get_attr_raw(parent.Get(), HYPERV_INSTANCE_ID_ITEM, + (const void**)&instanceID, &instanceIDLength, false); + if (fStatus != B_OK) + return; + + if (instanceIDLength != sizeof(*instanceID)) { + fStatus = B_BAD_VALUE; + return; + } + + // Hyper-V creates a device per IDE disk + // Use the target ID specified as part of the channel's instance ID + fMaxDevices = HV_SCSI_MAX_IDE_DEVICES; + fTargetID = static_cast(instanceID->data2); + TRACE("Controller is IDE using target ID %u\n", fTargetID); + } + + fStatus = gSCSI->alloc_dpc(&fBusDPC); + if (fStatus != B_OK) + return; + + fPacket = static_cast(malloc(HV_SCSI_RX_PKT_BUFFER_SIZE)); + if (fPacket == NULL) { + fStatus = B_NO_MEMORY; + return; + } + + for (uint32 i = 0; i < HV_SCSI_MAX_REQUESTS; i++) { + HyperVSCSIRequest* request = new(std::nothrow) HyperVSCSIRequest; + if (request == NULL) { + fStatus = B_NO_MEMORY; + return; + } + + fStatus = request->InitCheck(); + if (fStatus != B_OK) { + delete request; + return; + } + + fFreeRequests.Add(request); + } + + fStatus = fHyperV->open(fHyperVCookie, HV_SCSI_RING_SIZE, HV_SCSI_RING_SIZE, _CallbackHandler, + this); + if (fStatus != B_OK) { + ERROR("Failed to open channel (%s)\n", strerror(fStatus)); + return; + } + + fStatus = _BeginInit(); + if (fStatus != B_OK) { + ERROR("Failed to begin controller initialization (%s)\n", strerror(fStatus)); + return; + } + + fStatus = _NegotiateProtocol(); + if (fStatus != B_OK) { + ERROR("Failed to negotiate controller protocol (%s)\n", strerror(fStatus)); + return; + } + + fStatus = _QueryProperties(); + if (fStatus != B_OK) { + ERROR("Failed to query controller properties (%s)\n", strerror(fStatus)); + return; + } + + fStatus = _EndInit(); + if (fStatus != B_OK) { + ERROR("Failed to end controller initialization (%s)\n", strerror(fStatus)); + return; + } +} + + +HyperVSCSI::~HyperVSCSI() +{ + CALLED(); + + fHyperV->close(fHyperVCookie); + gSCSI->free_dpc(fBusDPC); + + MutexLocker requestLocker(fRequestLock); + + // Abort any active requests + HyperVSCSIRequestList::Iterator activeIterator = fActiveRequests.GetIterator(); + while (activeIterator.HasNext()) { + HyperVSCSIRequest* request = activeIterator.Next(); + request->Complete(SCSI_REQ_ABORTED); + request->Notify(); + delete request; + } + + HyperVSCSIRequestList::Iterator freeIterator = fFreeRequests.GetIterator(); + while (freeIterator.HasNext()) { + HyperVSCSIRequest* request = freeIterator.Next(); + delete request; + } + + mutex_destroy(&fRequestLock); + free(fPacket); +} + + +status_t +HyperVSCSI::StartIO(scsi_ccb* ccb) +{ + HyperVSCSIRequest* request = _GetRequest(); + if (request == NULL) + return B_BUSY; + request->SetCCB(ccb); + + if (ccb->target_id > fMaxDevices) { + request->Complete(SCSI_TID_INVALID); + _ReturnRequest(request); + return B_BAD_INDEX; + } + + if (ccb->target_lun > 0) { + request->Complete(SCSI_LUN_INVALID); + _ReturnRequest(request); + return B_BAD_INDEX; + } + + if (ccb->cdb_length > HV_SCSI_CDB_SIZE) { + request->Complete(SCSI_REQ_INVALID); + _ReturnRequest(request); + return B_BAD_VALUE; + } + + if (ccb->data_length > HV_SCSI_MAX_BUFFER_SIZE) { + request->Complete(SCSI_REQ_INVALID); + _ReturnRequest(request); + return B_NO_MEMORY; + } + + request->SetMessageType(HV_SCSI_MSGTYPE_EXECUTE_SRB); + hv_scsi_msg_request* message = &request->GetMessage()->request; + + // Hyper-V represents all attached devices under a single SCSI target + // This driver represents each device as a target with one LUN + message->target_id = fTargetID; + message->lun = ccb->target_id; + message->length = sizeof(*message) - sizeof(message->header) - _GetMessageLengthDelta(); + message->sense_info_length = _GetSenseLength(); + + if (!_IsLegacy()) { + message->win8_extension.timeout = 60; + message->win8_extension.srb_flags |= HV_SCSI_SRB_FLAGS_DISABLE_SYNC_TRANSFER; + } + + switch (ccb->flags & SCSI_DIR_MASK) { + case SCSI_DIR_IN: + message->direction = HV_SCSI_DIRECTION_READ; + message->data_length = ccb->data_length; + if (!_IsLegacy()) + message->win8_extension.srb_flags |= HV_SCSI_SRB_FLAGS_DATA_IN; + break; + + case SCSI_DIR_OUT: + message->direction = HV_SCSI_DIRECTION_WRITE; + message->data_length = ccb->data_length; + if (!_IsLegacy()) + message->win8_extension.srb_flags |= HV_SCSI_SRB_FLAGS_DATA_OUT; + break; + + case SCSI_DIR_NONE: + message->direction = HV_SCSI_DIRECTION_UNKNOWN; + break; + + default: + request->Complete(SCSI_REQ_INVALID); + _ReturnRequest(request); + return B_BAD_VALUE; + } + + message->cdb_length = ccb->cdb_length; + memcpy(message->cdb, ccb->cdb, ccb->cdb_length); + + TRACE("CDB[%u] %02X%02X %02X%02X %02X%02X %02X%02X %02X%02X %02X%02X %02X%02X %02X%02X\n", + message->cdb_length, message->cdb[0], message->cdb[1], message->cdb[2], message->cdb[3], + message->cdb[4], message->cdb[5], message->cdb[6], message->cdb[7], message->cdb[8], + message->cdb[9], message->cdb[10], message->cdb[11], message->cdb[12], message->cdb[13], + message->cdb[14], message->cdb[15]); + + request->SetTimeout(ccb->timeout > 0 ? HV_MS_TO_US(ccb->timeout * 1000) : HV_SCSI_TIMEOUT_US); + +#ifdef TRACE_HYPERV_SCSI + const char* dirStr = ""; + if (message->direction == HV_SCSI_DIRECTION_READ) + dirStr = " IN"; + else if (message->direction == HV_SCSI_DIRECTION_WRITE) + dirStr = " OUT"; + TRACE("SCSI%s request target %u LUN %u length 0x%X\n", dirStr, message->target_id, + message->lun, message->data_length); +#endif + + status_t status; + if (message->direction != HV_SCSI_DIRECTION_UNKNOWN) { + status = request->PrepareData(); + if (status != B_OK) { + ERROR("Failed to prepare for data transfer (%s)\n", strerror(status)); + request->Complete(SCSI_REQ_ABORTED); + _ReturnRequest(request); + return status; + } + } + + status = _SendRequest(request, true); + if (status != B_OK) { + ERROR("Failed to send request (%s)\n", strerror(status)); + request->Complete(SCSI_REQ_ABORTED); + _ReturnRequest(request); + return status; + } + + uint8 requestStatus = message->header.status == HV_SCSI_SUCCESS + ? SCSI_REQ_CMP : SCSI_REQ_CMP_ERR; + request->Complete(requestStatus); + + _ReturnRequest(request); + return B_OK; +} + + +uchar +HyperVSCSI::PathInquiry(scsi_path_inquiry* inquiry_data) +{ + if (GetBus() == NULL) + return SCSI_NO_HBA; + + inquiry_data->hba_inquiry = 0; + inquiry_data->hba_misc = 0; + inquiry_data->initiator_id = fMaxDevices; + inquiry_data->hba_queue_size = HV_SCSI_MAX_REQUESTS; + bzero(inquiry_data->vuhba_flags, sizeof(inquiry_data->vuhba_flags)); + + strlcpy(inquiry_data->sim_vid, "Haiku", SCSI_SIM_ID); + strlcpy(inquiry_data->hba_vid, "Hyper-V", SCSI_HBA_ID); + strlcpy(inquiry_data->sim_version, "1.0", SCSI_VERS); + snprintf(inquiry_data->hba_version, SCSI_VERS, "%u.%u", GET_SCSI_VERSION_MAJOR(fVersion), + GET_SCSI_VERSION_MINOR(fVersion)); + strlcpy(inquiry_data->controller_family, "Hyper-V", SCSI_FAM_ID); + strlcpy(inquiry_data->controller_type, "Hyper-V", SCSI_TYPE_ID); + + return SCSI_REQ_CMP; +} + + +uchar +HyperVSCSI::ResetBus() +{ + HyperVSCSIRequest* request = _GetRequest(); + if (request == NULL) + return SCSI_REQ_ABORTED; + + request->SetMessageType(HV_SCSI_MSGTYPE_RESET_BUS); + status_t status = _SendRequest(request, true); + if (status == B_OK) + status = request->GetMessageStatus() == HV_SCSI_SUCCESS ? B_OK : B_IO_ERROR; + + _ReturnRequest(request); + return status == B_OK ? SCSI_REQ_CMP : SCSI_REQ_ABORTED; +} + + +uchar +HyperVSCSI::ResetDevice(uchar targetID, uchar targetLUN) +{ + if (targetID > fMaxDevices) + return SCSI_TID_INVALID; + if (targetLUN > 0) + return SCSI_LUN_INVALID; + + HyperVSCSIRequest* request = _GetRequest(); + if (request == NULL) + return SCSI_REQ_ABORTED; + + request->SetMessageType(HV_SCSI_MSGTYPE_RESET_LUN); + hv_scsi_msg_request* message = &request->GetMessage()->request; + + message->target_id = fTargetID; + message->lun = targetID; + + status_t status = _SendRequest(request, true); + if (status == B_OK) + status = request->GetMessageStatus() == HV_SCSI_SUCCESS ? B_OK : B_IO_ERROR; + + _ReturnRequest(request); + return status == B_OK ? SCSI_REQ_CMP : SCSI_REQ_ABORTED; +} + + +/*static*/ void +HyperVSCSI::_CallbackHandler(void* data) +{ + HyperVSCSI* scsi = reinterpret_cast(data); + scsi->_Callback(); +} + + +uint8 +HyperVSCSI::_GetSenseLength() const +{ + return _IsLegacy() ? HV_SCSI_LEGACY_SENSE_SIZE : HV_SCSI_SENSE_SIZE; +} + +uint32 +HyperVSCSI::_GetMessageLengthDelta() const +{ + return _IsLegacy() ? sizeof(hv_scsi_request_win8_extension) : 0; +} + + +void +HyperVSCSI::_Callback() +{ + while (true) { + uint32 length = HV_SCSI_RX_PKT_BUFFER_SIZE; + uint32 headerLength; + uint32 messageLength; + + status_t status = fHyperV->read_packet(fHyperVCookie, fPacket, &length, &headerLength, + &messageLength); + if (status == B_DEV_NOT_READY) { + break; + } else if (status != B_OK) { + ERROR("Failed to read packet (%s)\n", strerror(status)); + break; + } + + vmbus_pkt_header* header = reinterpret_cast(fPacket); + hv_scsi_msg* message = reinterpret_cast(fPacket + headerLength); + + switch (message->header.type) { + case HV_SCSI_MSGTYPE_COMPLETE_IO: + _CompleteIO(header->transaction_id, message); + break; + + case HV_SCSI_MSGTYPE_REMOVE_DEVICE: + case HV_SCSI_MSGTYPE_ENUM_BUS: + // Device added/removed, trigger rescan on seperate thread + gSCSI->schedule_dpc(fBus, fBusDPC, _RescanDPCHandler, this); + break; + + default: + TRACE("Unknown message type %u\n", message->header.type); + break; + } + } +} + + +/*static*/ void +HyperVSCSI::_RescanDPCHandler(void* data) +{ + HyperVSCSI* scsi = reinterpret_cast(data); + scsi->_RescanBus(); +} + +void +HyperVSCSI::_RescanBus() +{ + TRACE("Rescanning bus\n"); + + device_node *childNode = NULL; + const device_attr attrs[] = { { NULL } }; + if (gDeviceManager->get_next_child_node(fNode, attrs, &childNode) != B_OK) { + ERROR("Failed to find child node for %p\n", fNode); + return; + } + + gDeviceManager->rescan_node(childNode); + gDeviceManager->put_node(childNode); +} + + +status_t +HyperVSCSI::_SendRequest(HyperVSCSIRequest* request, bool wait) +{ + hv_scsi_msg* message = request->GetMessage(); + scsi_ccb* ccb = request->GetCCB(); + + message->header.flags = HV_SCSI_FLAG_REQUEST_COMPLETION; + + MutexLocker requestLocker(fRequestLock); + fActiveRequests.Add(request); + requestLocker.Unlock(); + + ConditionVariableEntry entry; + if (wait) + request->AddWaiter(&entry); + + // Data requests are sent as GPA packets, all others as standard inband + status_t status; + if (ccb != NULL && message->request.direction != HV_SCSI_DIRECTION_UNKNOWN) { + vmbus_gpa_range* gpaRange = request->GetGPARange(); + uint32 gpaLength = request->GetGPARangeLength(); + + status = fHyperV->write_gpa_packet(fHyperVCookie, 1, gpaRange, gpaLength, message, + sizeof(*message) - _GetMessageLengthDelta(), true, (uint64)request); + } else { + status = fHyperV->write_packet(fHyperVCookie, VMBUS_PKTTYPE_DATA_INBAND, message, + sizeof(*message) - _GetMessageLengthDelta(), true, (uint64)request); + } + + if (status != B_OK) { + ERROR("Failed to submit request %p (%s)\n", request, strerror(status)); + requestLocker.Lock(); + fActiveRequests.Remove(request); + return status; + } + + if (wait) { + status = entry.Wait(B_RELATIVE_TIMEOUT, request->GetTimeout()); + if (status != B_OK) { + ERROR("Failed to wait for request %p (%s)\n", request, strerror(status)); + requestLocker.Lock(); + if (fActiveRequests.Contains(request)) + fActiveRequests.Remove(request); + else + ERROR("Request %p not present in active list\n", request); + return status; + } + } + + return B_OK; +} + + +HyperVSCSIRequest* +HyperVSCSI::_GetRequest() +{ + MutexLocker requestLocker(fRequestLock); + + HyperVSCSIRequest* request = fFreeRequests.RemoveHead(); + if (request != NULL) + request->Reset(); + + return request; +} + + +void +HyperVSCSI::_ReturnRequest(HyperVSCSIRequest* request) +{ + MutexLocker requestLocker(fRequestLock); + fFreeRequests.Add(request); +} + + +void +HyperVSCSI::_CompleteIO(uint64 transactionID, hv_scsi_msg* message) +{ + HyperVSCSIRequest* request = (HyperVSCSIRequest*)transactionID; + MutexLocker requestLocker(fRequestLock); + + if (!fActiveRequests.Contains(request)) { + ERROR("Attempted to complete unknown request %p\n", request); + return; + } + + request->SetMessageData(message); + + fActiveRequests.Remove(request); + requestLocker.Unlock(); + + request->Notify(); +} + + +status_t +HyperVSCSI::_BeginInit() +{ + HyperVSCSIRequest* request = _GetRequest(); + if (request == NULL) + return B_BUSY; + + request->SetMessageType(HV_SCSI_MSGTYPE_BEGIN_INIT); + status_t status = _SendRequest(request, true); + if (status == B_OK) + status = request->GetMessageStatus() == HV_SCSI_SUCCESS ? B_OK : B_IO_ERROR; + + _ReturnRequest(request); + return status; +} + + +status_t +HyperVSCSI::_NegotiateProtocol() +{ + HyperVSCSIRequest* request = _GetRequest(); + if (request == NULL) + return B_BUSY; + + status_t status = B_UNSUPPORTED; + for (uint32 i = 0; i < hv_scsi_version_count; i++) { + request->Reset(); + request->SetMessageType(HV_SCSI_MSGTYPE_QUERY_PROTOCOL_VER); + request->GetMessage()->protocol.version = hv_scsi_versions[i]; + request->GetMessage()->protocol.revision = 0; + + status = _SendRequest(request, true); + if (status == B_OK) + status = request->GetMessageStatus() == HV_SCSI_SUCCESS ? B_OK : B_UNSUPPORTED; + + if (status == B_OK) { + fVersion = hv_scsi_versions[i]; + TRACE("SCSI protocol version %u.%u, sense 0x%X, delta 0x%X\n", + GET_SCSI_VERSION_MAJOR(fVersion), GET_SCSI_VERSION_MINOR(fVersion), + _GetSenseLength(), _GetMessageLengthDelta()); + break; + } + } + + _ReturnRequest(request); + return status; +} + + +status_t +HyperVSCSI::_QueryProperties() +{ + HyperVSCSIRequest* request = _GetRequest(); + if (request == NULL) + return B_BUSY; + + request->SetMessageType(HV_SCSI_MSGTYPE_QUERY_PROPERTIES); + status_t status = _SendRequest(request, true); + if (status == B_OK) + status = request->GetMessageStatus() == HV_SCSI_SUCCESS ? B_OK : B_IO_ERROR; + + if (status == B_OK) { + if ((request->GetMessage()->channel_properties.flags & HV_SCSI_FLAG_SUPPORTS_MULTI_CHANNEL) + != 0) + fMaxSubChannels = request->GetMessage()->channel_properties.max_channels; + fMaxTransferBytes = request->GetMessage()->channel_properties.max_transfer_bytes; + TRACE("SCSI max sub channels %u max transfer bytes 0x%X\n", fMaxSubChannels, + fMaxTransferBytes); + } + + _ReturnRequest(request); + return B_OK; +} + + +status_t +HyperVSCSI::_EndInit() +{ + HyperVSCSIRequest* request = _GetRequest(); + if (request == NULL) + return B_BUSY; + + request->SetMessageType(HV_SCSI_MSGTYPE_END_INIT); + status_t status = _SendRequest(request, true); + if (status == B_OK) + status = request->GetMessageStatus() == HV_SCSI_SUCCESS ? B_OK : B_IO_ERROR; + + _ReturnRequest(request); + return status; +} diff --git a/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSI.h b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSI.h new file mode 100644 index 0000000000..0ce4563bb5 --- /dev/null +++ b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSI.h @@ -0,0 +1,103 @@ +/* + * Copyright 2026 John Davis. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _HYPERV_SCSI_H_ +#define _HYPERV_SCSI_H_ + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "HyperVSCSIProtocol.h" +#include "HyperVSCSIRequest.h" + + +//#define TRACE_HYPERV_SCSI +#ifdef TRACE_HYPERV_SCSI +# define TRACE(x...) dprintf("\33[94mhyperv_scsi:\33[0m " x) +#else +# define TRACE(x...) ; +#endif +#define TRACE_ALWAYS(x...) dprintf("\33[94mhyperv_scsi:\33[0m " x) +#define ERROR(x...) dprintf("\33[94mhyperv_scsi:\33[0m " x) +#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__) + + +#define HYPERV_SCSI_ID_GENERATOR "hyperv_scsi/id" +#define HYPERV_SCSI_ID_ITEM "hyperv_scsi/id" +#define HYPERV_SCSI_DRIVER_MODULE_NAME "busses/scsi/hyperv_scsi/driver_v1" +#define HYPERV_SCSI_SIM_MODULE_NAME "busses/scsi/hyperv_scsi/sim/driver_v1" +#define HYPERV_SCSI_SIM_PRETTY_NAME "Hyper-V SCSI Interface" + + +extern device_manager_info* gDeviceManager; +extern scsi_for_sim_interface* gSCSI; + + +class HyperVSCSI { +public: + HyperVSCSI(device_node* node); + ~HyperVSCSI(); + status_t InitCheck() const { return fStatus; } + scsi_bus GetBus() const { return fBus; } + void SetBus(scsi_bus bus) { fBus = bus; } + + status_t StartIO(scsi_ccb* ccb); + uchar PathInquiry(scsi_path_inquiry* inquiry_data); + uchar ResetBus(); + uchar ResetDevice(uchar targetID, uchar targetLUN); + +private: + bool _IsLegacy() const { return fVersion < HV_SCSI_VERSION_WIN8; } + uint8 _GetSenseLength() const; + uint32 _GetMessageLengthDelta() const; + static void _CallbackHandler(void* data); + void _Callback(); + static void _RescanDPCHandler(void* data); + void _RescanBus(); + status_t _SendRequest(HyperVSCSIRequest* request, bool wait); + HyperVSCSIRequest* _GetRequest(); + void _ReturnRequest(HyperVSCSIRequest* request); + void _CompleteIO(uint64 transactionID, hv_scsi_msg* message); + status_t _BeginInit(); + status_t _NegotiateProtocol(); + status_t _QueryProperties(); + status_t _EndInit(); + +private: + device_node* fNode; + scsi_bus fBus; + scsi_dpc_cookie fBusDPC; + status_t fStatus; + + bool fIsIDE; + uint8 fTargetID; + uint8 fMaxDevices; + uint16 fVersion; + uint16 fMaxSubChannels; + uint32 fMaxTransferBytes; + uint8* fPacket; + + HyperVSCSIRequestList fFreeRequests; + HyperVSCSIRequestList fActiveRequests; + mutex fRequestLock; + + hyperv_device_interface* fHyperV; + hyperv_device fHyperVCookie; +}; + + +#endif diff --git a/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIModule.cpp b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIModule.cpp new file mode 100644 index 0000000000..4a5e698eab --- /dev/null +++ b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIModule.cpp @@ -0,0 +1,310 @@ +/* + * Copyright 2026 John Davis. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "HyperVSCSI.h" + + +device_manager_info* gDeviceManager; +scsi_for_sim_interface* gSCSI; + + +static status_t +hyperv_scsi_init_bus(device_node* node, void** _driverCookie) +{ + CALLED(); + + HyperVSCSI* scsi = new(std::nothrow) HyperVSCSI(node); + if (scsi == NULL) + return B_NO_MEMORY; + + status_t status = scsi->InitCheck(); + if (status != B_OK) { + delete scsi; + return status; + } + + *_driverCookie = scsi; + return B_OK; +} + + +static void +hyperv_scsi_uninit_bus(void* driverCookie) +{ + CALLED(); + HyperVSCSI* scsi = reinterpret_cast(driverCookie); + delete scsi; +} + + +static void +hyperv_scsi_set_scsi_bus(scsi_sim_cookie cookie, scsi_bus bus) +{ + CALLED(); + HyperVSCSI* scsi = reinterpret_cast(cookie); + scsi->SetBus(bus); +} + + +static void +hyperv_scsi_scsi_io(scsi_sim_cookie cookie, scsi_ccb* ccb) +{ + CALLED(); + HyperVSCSI* scsi = reinterpret_cast(cookie); + if (scsi->StartIO(ccb) == B_BUSY) + gSCSI->requeue(ccb, true); +} + + +static uchar +hyperv_scsi_abort(scsi_sim_cookie cookie, scsi_ccb* ccb_to_abort) +{ + CALLED(); + return SCSI_REQ_CMP; +} + + +static uchar +hyperv_scsi_reset_device(scsi_sim_cookie cookie, uchar target_id, uchar target_lun) +{ + CALLED(); + HyperVSCSI* scsi = reinterpret_cast(cookie); + return scsi->ResetDevice(target_id, target_lun); +} + + +static uchar +hyperv_scsi_term_io(scsi_sim_cookie cookie, scsi_ccb* ccb_to_terminate) +{ + CALLED(); + return SCSI_REQ_CMP; +} + + +static uchar +hyperv_scsi_path_inquiry(scsi_sim_cookie cookie, scsi_path_inquiry* inquiry_data) +{ + CALLED(); + HyperVSCSI* scsi = reinterpret_cast(cookie); + return scsi->PathInquiry(inquiry_data); +} + + +static uchar +hyperv_scsi_scan_bus(scsi_sim_cookie cookie) +{ + // Function is called prior to a full bus scan + CALLED(); + return SCSI_REQ_CMP; +} + + +static uchar +hyperv_scsi_reset_bus(scsi_sim_cookie cookie) +{ + CALLED(); + HyperVSCSI* scsi = reinterpret_cast(cookie); + return scsi->ResetBus(); +} + + +static void +hyperv_scsi_get_restrictions(scsi_sim_cookie cookie, uchar target_id, bool* is_atapi, + bool* no_autosense, uint32* max_blocks) +{ + CALLED(); + + // SCSI CD-ROM and fixed disks are both supported on Gen2 VMs + // Always indicate ATAPI for compatibility with both and older versions of Hyper-V + *is_atapi = true; + *no_autosense = false; + *max_blocks = HV_SCSI_MAX_BLOCK_COUNT; +} + + +static status_t +hyperv_scsi_ioctl(scsi_sim_cookie, uint8 targetID, uint32 op, void* buffer, size_t length) +{ + CALLED(); + return B_DEV_INVALID_IOCTL; +} + + +static float +hyperv_scsi_supports_device(device_node* parent) +{ + CALLED(); + + // Check if parent is the Hyper-V bus manager + const char* bus; + if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK) + return -1; + + if (strcmp(bus, HYPERV_BUS_NAME) != 0) + return 0.0f; + + // Check if parent is a Hyper-V SCSI controller + const char* type; + if (gDeviceManager->get_attr_string(parent, HYPERV_DEVICE_TYPE_STRING_ITEM, &type, false) + != B_OK) + return 0.0f; + + // TODO: This driver can support the IDE acceleration device, but as only fixed disks are + // exposed here, there will need to be some logic either here or in the ATA PCI driver to + // prevent ATA disks from being handled by the ATA driver + // Hyper-V exposes two dual channel Intel PIIX4 adapters on Gen1 VMs only + bool isIDE = false; // strcmp(type, VMBUS_TYPE_IDE) == 0; + if (strcmp(type, VMBUS_TYPE_SCSI) != 0 && !isIDE) + return 0.0f; + + TRACE("Hyper-V %s controller found!\n", isIDE ? "IDE" : "SCSI"); + return 0.8f; +} + + +static status_t +hyperv_scsi_register_device(device_node* parent) +{ + CALLED(); + + // Check if parent is a Hyper-V IDE controller as it has different LUN/target limits + const char* type; + status_t status = gDeviceManager->get_attr_string(parent, HYPERV_DEVICE_TYPE_STRING_ITEM, + &type, false); + if (status != B_OK) + return status; + + bool isIDE = strcmp(type, VMBUS_TYPE_IDE) == 0; + device_attr attributes[] = { + { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, + { .string = isIDE ? HYPERV_PRETTYNAME_IDE : HYPERV_PRETTYNAME_SCSI }}, + + { SCSI_DEVICE_MAX_TARGET_COUNT, B_UINT32_TYPE, + { .ui32 = isIDE ? HV_SCSI_MAX_IDE_DEVICES : HV_SCSI_MAX_SCSI_DEVICES }}, + { SCSI_DEVICE_MAX_LUN_COUNT, B_UINT32_TYPE, + { .ui32 = 1 }}, + + { B_DMA_ALIGNMENT, B_UINT32_TYPE, + { .ui32 = 1 }}, // Word alignment + { B_DMA_MAX_SEGMENT_BLOCKS, B_UINT32_TYPE, + { .ui32 = HV_PAGE_SIZE }}, + { B_DMA_MAX_SEGMENT_COUNT, B_UINT32_TYPE, + { .ui32 = HV_SCSI_MAX_BUFFER_SEGMENTS }}, + + { NULL } + }; + + return gDeviceManager->register_node(parent, HYPERV_SCSI_DRIVER_MODULE_NAME, attributes, NULL, + NULL); +} + + +static status_t +hyperv_scsi_init_driver(device_node* node, void** _driverCookie) +{ + CALLED(); + *_driverCookie = node; + return B_OK; +} + + +static status_t +hyperv_scsi_register_child_devices(void* driverCookie) +{ + CALLED(); + device_node* node = reinterpret_cast(driverCookie); + + int32 id = gDeviceManager->create_id(HYPERV_SCSI_ID_GENERATOR); + if (id < 0) + return id; + + device_attr attributes[] = { + { B_DEVICE_FIXED_CHILD, B_STRING_TYPE, + { .string = SCSI_FOR_SIM_MODULE_NAME }}, + { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, + { .string = HYPERV_SCSI_SIM_PRETTY_NAME }}, + + { SCSI_DESCRIPTION_CONTROLLER_NAME, B_STRING_TYPE, + { .string = HYPERV_SCSI_DRIVER_MODULE_NAME }}, + + { HYPERV_SCSI_ID_ITEM, B_UINT32_TYPE, + { .ui32 = static_cast(id) }}, + + { NULL } + }; + + status_t status = gDeviceManager->register_node(node, HYPERV_SCSI_SIM_MODULE_NAME, attributes, + NULL, NULL); + if (status != B_OK) + gDeviceManager->free_id(HYPERV_SCSI_ID_GENERATOR, id); + + return status; +} + + +static scsi_sim_interface sHyperVSCSISimInterface = { + { + { + HYPERV_SCSI_SIM_MODULE_NAME, + 0, + NULL + }, + + NULL, // supports device + NULL, // register device + hyperv_scsi_init_bus, + hyperv_scsi_uninit_bus, + NULL, // register child devices + NULL, // rescan child devices + NULL, // device removed + NULL, // suspend + NULL // resume + }, + + hyperv_scsi_set_scsi_bus, + hyperv_scsi_scsi_io, + hyperv_scsi_abort, + hyperv_scsi_reset_device, + hyperv_scsi_term_io, + hyperv_scsi_path_inquiry, + hyperv_scsi_scan_bus, + hyperv_scsi_reset_bus, + hyperv_scsi_get_restrictions, + hyperv_scsi_ioctl +}; + + +static driver_module_info sHyperVSCSIModule = { + { + HYPERV_SCSI_DRIVER_MODULE_NAME, + 0, + NULL + }, + + hyperv_scsi_supports_device, + hyperv_scsi_register_device, + hyperv_scsi_init_driver, + NULL, // uninit driver + hyperv_scsi_register_child_devices, + NULL, // rescan child devices + NULL, // device removed + NULL, // suspend + NULL // resume +}; + + +module_dependency module_dependencies[] = { + { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager }, + { SCSI_FOR_SIM_MODULE_NAME, (module_info**)&gSCSI }, + {} +}; + + +module_info *modules[] = { + (module_info*)&sHyperVSCSIModule, + (module_info*)&sHyperVSCSISimInterface, + NULL +}; diff --git a/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIProtocol.h b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIProtocol.h new file mode 100644 index 0000000000..a9cb777704 --- /dev/null +++ b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIProtocol.h @@ -0,0 +1,188 @@ +/* + * Copyright 2026 John Davis. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _HYPERV_SCSI_PROTOCOL_H_ +#define _HYPERV_SCSI_PROTOCOL_H_ + + +#include + + +#define HV_SCSI_RING_SIZE 0x200000 +#define HV_SCSI_RX_PKT_BUFFER_SIZE 512 + +#define HV_SCSI_MAX_SCSI_DEVICES 64U +#define HV_SCSI_MAX_IDE_DEVICES 1U + +#define HV_SCSI_CDB_SIZE 0x10 +#define HV_SCSI_SENSE_SIZE 0x14 +#define HV_SCSI_LEGACY_SENSE_SIZE 0x12 + +#define HV_SCSI_MAX_BLOCK_SIZE 2048 +#define HV_SCSI_MAX_BLOCK_COUNT 128 +#define HV_SCSI_MAX_BUFFER_SIZE 0x40000 +#define HV_SCSI_MAX_BUFFER_SEGMENTS (HV_SCSI_MAX_BUFFER_SIZE / HV_PAGE_SIZE) +#define HV_SCSI_MAX_BUFFER_PAGES (HV_SCSI_MAX_BUFFER_SEGMENTS + 1) + +#define HV_SCSI_TIMEOUT_US HV_MS_TO_US(10000) +#define HV_SCSI_MAX_REQUESTS 2 + + +// Hyper-V SCSI version +#define MAKE_SCSI_VERSION(major, minor) ((((major) << 8) & 0xFF00) | ((minor) & 0x00FF)) +#define GET_SCSI_VERSION_MAJOR(version) (((version) >> 8) & 0xFF) +#define GET_SCSI_VERSION_MINOR(version) ((version) & 0xFF) + +#define HV_SCSI_VERSION_WIN2008 MAKE_SCSI_VERSION(2, 0) +#define HV_SCSI_VERSION_WIN2008R2 MAKE_SCSI_VERSION(4, 2) +#define HV_SCSI_VERSION_WIN8 MAKE_SCSI_VERSION(5, 1) +#define HV_SCSI_VERSION_WIN81 MAKE_SCSI_VERSION(6, 0) +#define HV_SCSI_VERSION_WIN10 MAKE_SCSI_VERSION(6, 1) + + +static const uint32 hv_scsi_versions[] = { + HV_SCSI_VERSION_WIN10, + HV_SCSI_VERSION_WIN81, + HV_SCSI_VERSION_WIN8, + HV_SCSI_VERSION_WIN2008R2, + HV_SCSI_VERSION_WIN2008 +}; +static const uint32 hv_scsi_version_count = sizeof(hv_scsi_versions) + / sizeof(hv_scsi_versions[0]); + + +// SCSI request direction +enum { + HV_SCSI_DIRECTION_WRITE = 0, + HV_SCSI_DIRECTION_READ = 1, + HV_SCSI_DIRECTION_UNKNOWN = 2 +}; + + +#define HV_SCSI_SRB_FLAGS_DISABLE_SYNC_TRANSFER (1 << 3) +#define HV_SCSI_SRB_FLAGS_DISABLE_AUTOSENSE (1 << 5) +#define HV_SCSI_SRB_FLAGS_DATA_IN (1 << 6) +#define HV_SCSI_SRB_FLAGS_DATA_OUT (1 << 7) + + +// SCSI request extension on Windows 8 / Windows Server 2012 and newer +typedef struct { + uint16 reserved; + uint8 queue_tag; + uint8 queue_action; + uint32 srb_flags; + uint32 timeout; + uint32 queue_sort_by; +} _PACKED hv_scsi_request_win8_extension; + + +// SCSI message types +enum { + HV_SCSI_MSGTYPE_COMPLETE_IO = 1, + HV_SCSI_MSGTYPE_REMOVE_DEVICE = 2, + HV_SCSI_MSGTYPE_EXECUTE_SRB = 3, + HV_SCSI_MSGTYPE_RESET_LUN = 4, + HV_SCSI_MSGTYPE_RESET_ADAPTER = 5, + HV_SCSI_MSGTYPE_RESET_BUS = 6, + HV_SCSI_MSGTYPE_BEGIN_INIT = 7, + HV_SCSI_MSGTYPE_END_INIT = 8, + HV_SCSI_MSGTYPE_QUERY_PROTOCOL_VER = 9, + HV_SCSI_MSGTYPE_QUERY_PROPERTIES = 10, + HV_SCSI_MSGTYPE_ENUM_BUS = 11, + HV_SCSI_MSGTYPE_FCHBA_DATA = 12, + HV_SCSI_MSGTYPE_CREATE_SUB_CHANNELS = 13 +}; + + +#define HV_SCSI_FLAG_REQUEST_COMPLETION 1 +#define HV_SCSI_SUCCESS 0 + + +// SCSI message header +typedef struct { + uint32 type; + uint32 flags; + uint32 status; +} _PACKED hv_scsi_msg_header; + + +// SCSI request message sent to Hyper-V +typedef struct { + hv_scsi_msg_header header; + + uint16 length; + uint8 srb_status; + uint8 scsi_status; + + uint8 port; + uint8 path_id; + uint8 target_id; + uint8 lun; + + uint8 cdb_length; + uint8 sense_info_length; + uint8 direction; + uint8 reserved; + uint32 data_length; + + union { + uint8 cdb[HV_SCSI_CDB_SIZE]; + uint8 sense[HV_SCSI_SENSE_SIZE]; + }; + + hv_scsi_request_win8_extension win8_extension; +} _PACKED hv_scsi_msg_request; + + +#define HV_SCSI_FLAG_SUPPORTS_MULTI_CHANNEL 1 + +// SCSI channel properties message +typedef struct { + hv_scsi_msg_header header; + + uint32 reserved1; + uint16 max_channels; + uint16 reserved2; + + uint32 flags; + uint32 max_transfer_bytes; + + uint64 reserved3; +} _PACKED hv_scsi_msg_channel_properties; + + +// SCSI protocol message +typedef struct { + hv_scsi_msg_header header; + + uint16 version; + uint16 revision; // Always zero for this driver +} _PACKED hv_scsi_msg_protocol; + + +// SCSI Fibre Channel WWN message +typedef struct { + uint8 primary_active; + uint8 reserved[3]; + uint8 primary_port_wwn[8]; + uint8 primary_node_wwn[8]; + uint8 secondary_port_wwn[8]; + uint8 secondary_node_wwn[8]; +} _PACKED hv_scsi_msg_fibre_channel_wwn; + + +// SCSI combined message +typedef union { + hv_scsi_msg_header header; + hv_scsi_msg_request request; + hv_scsi_msg_channel_properties channel_properties; + hv_scsi_msg_protocol protocol; + hv_scsi_msg_fibre_channel_wwn fibre_channel_wwn; + + uint16 sub_channels; + uint8 padding[sizeof(hv_scsi_msg_header) + 0x34]; +} _PACKED hv_scsi_msg; + + +#endif diff --git a/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIRequest.cpp b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIRequest.cpp new file mode 100644 index 0000000000..e04d799a98 --- /dev/null +++ b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIRequest.cpp @@ -0,0 +1,241 @@ +/* + * Copyright 2026 John Davis. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "HyperVSCSI.h" +#include "HyperVSCSIRequest.h" + + +#include + + +HyperVSCSIRequest::HyperVSCSIRequest() + : + fStatus(B_NO_INIT), + fBounceArea(0), + fBounceBuffer(NULL), + fBounceBufferInUse(false), + fGPARange(NULL), + fGPARangeLength(0) +{ + fConditionVariable.Init(this, "hyperv_scsi request"); + + fBounceArea = create_area("hyperv_scsi buffer", &fBounceBuffer, B_ANY_KERNEL_ADDRESS, + HV_SCSI_MAX_BUFFER_SIZE, B_CONTIGUOUS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); + if (fBounceArea < B_OK) { + fStatus = fBounceArea; + return; + } + + physical_entry entry; + fStatus = get_memory_map(fBounceBuffer, HV_SCSI_MAX_BUFFER_SIZE, &entry, 1); + if (fStatus != B_OK) + return; + fBounceBufferPhys = entry.address; + + fGPARange = (vmbus_gpa_range*)malloc(offsetof(vmbus_gpa_range, + page_nums[HV_SCSI_MAX_BUFFER_PAGES])); + if (fGPARange == NULL) { + fStatus = B_NO_MEMORY; + return; + } + + fStatus = B_OK; + Reset(); +} + + +HyperVSCSIRequest::~HyperVSCSIRequest() +{ + free(fGPARange); + delete_area(fBounceArea); +} + + +void +HyperVSCSIRequest::Reset() +{ + bzero(&fMessage, sizeof(fMessage)); + SetTimeout(HV_SCSI_TIMEOUT_US); + + fCCB = NULL; + fBounceBufferInUse = false; + fGPARangeLength = 0; +} + + +status_t +HyperVSCSIRequest::PrepareData() +{ + if (fCCB == NULL) + return B_BAD_VALUE; + + uint32 flags = fCCB->flags & SCSI_DIR_MASK; + if (flags != SCSI_DIR_IN && flags != SCSI_DIR_OUT) + return B_BAD_VALUE; + + if (fCCB->sg_count == 0) + return B_BAD_VALUE; + + if (fCCB->sg_count < 2) + return _FillGPARange(fCCB->sg_list, fCCB->sg_count, fCCB->data_length); + + // Hyper-V supports multiple discontiguous buffers, but the SCSI controller device does not + // Bounce buffer will be used if there are any non-page sized entries, or holes + fBounceBufferInUse = false; + for (uint16 i = 0; i < fCCB->sg_count; i++) { + phys_addr_t offset = fCCB->sg_list[i].address & HV_PAGE_MASK; + phys_size_t size = fCCB->sg_list[i].size; + + if (i == 0) { + // Ensure first entry does not leave a hole between it and the next entry + if (offset + size != HV_PAGE_SIZE) { + fBounceBufferInUse = true; + break; + } + } else if (i == fCCB->sg_count - 1) { + // Ensure last entry is page-aligned + if (offset != 0) { + fBounceBufferInUse = true; + break; + } + } else { + // Ensure all other entries are page-aligned and an entire page + if (offset != 0 || size != HV_PAGE_SIZE) { + fBounceBufferInUse = true; + break; + } + } + } + + if (!fBounceBufferInUse) + return _FillGPARange(fCCB->sg_list, fCCB->sg_count, fCCB->data_length); + + if (fCCB->data_length > HV_SCSI_MAX_BUFFER_SIZE) + return B_NO_MEMORY; + + TRACE("Bounce buffer used\n"); + + // Copy data to bounce buffer if a write was requested + if ((fCCB->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT) { + uint8* bounceBufferPtr = static_cast(fBounceBuffer); + for (uint32 i = 0; i < fCCB->sg_count; i++) { + vm_memcpy_from_physical(bounceBufferPtr, fCCB->sg_list[i].address, + fCCB->sg_list[i].size, false); + bounceBufferPtr += fCCB->sg_list[i].size; + } + } + + physical_entry entry; + entry.address = fBounceBufferPhys; + entry.size = fCCB->data_length; + return _FillGPARange(&entry, 1, fCCB->data_length); +} + + +void +HyperVSCSIRequest::Complete(uint8 status) +{ + TRACE("Complete request %p status 0x%X\n", this, status); + + if (fCCB != NULL) { + fCCB->subsys_status = status; + if (fMessage.header.type == HV_SCSI_MSGTYPE_COMPLETE_IO) { + if (fCCB->data_length == 0) + fCCB->data_resid = 0; + else + fCCB->data_resid = fCCB->data_length - fMessage.request.data_length; + + TRACE(" msg status 0x%X SRB status 0x%X SCSI status 0x%X data resid %u\n", + fMessage.header.status, fMessage.request.srb_status, fMessage.request.scsi_status, + fCCB->data_resid); + + // Copy data from bounce buffer if used + if (fBounceBufferInUse) { + uint8* bounceBufferPtr = static_cast(fBounceBuffer); + for (uint32 i = 0; i < fCCB->sg_count; i++) { + vm_memcpy_to_physical(fCCB->sg_list[i].address, bounceBufferPtr, + fCCB->sg_list[i].size, false); + bounceBufferPtr += fCCB->sg_list[i].size; + } + } + + fCCB->subsys_status = fMessage.request.srb_status; + fCCB->device_status = fMessage.request.scsi_status; + if (fMessage.request.scsi_status == SCSI_STATUS_CHECK_CONDITION + && (fMessage.request.srb_status & SCSI_AUTOSNS_VALID) != 0 + && (fCCB->flags & SCSI_DIS_AUTOSENSE) == 0) { + uint32 senseLength = min_c(sizeof(fCCB->sense), + fMessage.request.sense_info_length); + TRACE(" sense length 0x%X\n", senseLength); + + memcpy(fCCB->sense, fMessage.request.sense, senseLength); + fCCB->sense_resid = sizeof(fCCB->sense) - senseLength; + } + } + + gSCSI->finished(fCCB, 1); + } +} + + +void +HyperVSCSIRequest::SetMessageData(hv_scsi_msg* message) +{ + memcpy(&fMessage, message, sizeof(fMessage)); +} + + +void +HyperVSCSIRequest::AddWaiter(ConditionVariableEntry* entry) +{ + fConditionVariable.Add(entry); +} + + +void +HyperVSCSIRequest::Notify() +{ + fConditionVariable.NotifyAll(); +} + + +status_t +HyperVSCSIRequest::_FillGPARange(const physical_entry* sgList, uint32 sgCount, uint32 dataLength) +{ + // It is assumed there are no holes or non-page aligned segments + // Validation is performed in PrepareData() + + uint32 totalPageCount = 0; + fGPARange->offset = sgList[0].address & HV_PAGE_MASK; + fGPARange->length = dataLength; + for (uint32 i = 0; i < sgCount; i++) { + phys_addr_t address = sgList[i].address; + phys_size_t length = sgList[i].size; + uint32 pageCount = HV_BYTES_TO_SPAN_PAGES(address, length); + + TRACE("SG[%u] %u pages total %u\n", i, pageCount, totalPageCount); + if (totalPageCount + pageCount > HV_SCSI_MAX_BUFFER_PAGES) { + ERROR("Too many pages in sg list\n"); + return B_NO_MEMORY; + } + + uint64 pageNumber = address >> HV_PAGE_SHIFT; + for (uint32 p = totalPageCount; p < totalPageCount + pageCount; p++) + fGPARange->page_nums[p] = pageNumber++; + totalPageCount += pageCount; + } + + fGPARangeLength = sizeof(*fGPARange) + (sizeof(fGPARange->page_nums[0]) * totalPageCount); + +#ifdef TRACE_HYPERV_SCSI + TRACE("SCSI range 0x%X len 0x%X page count %u\n", fGPARange->offset, fGPARange->length, + totalPageCount); + for (uint32 i = 0; i < totalPageCount; i++) + TRACE(" page[%u]: %" PRIu64 "\n", i, fGPARange->page_nums[i]); +#endif + + return B_OK; +} diff --git a/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIRequest.h b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIRequest.h new file mode 100644 index 0000000000..5523dff9f4 --- /dev/null +++ b/src/add-ons/kernel/busses/scsi/hyperv/HyperVSCSIRequest.h @@ -0,0 +1,72 @@ +/* + * Copyright 2026 John Davis. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _HYPERV_SCSI_REQUEST_H_ +#define _HYPERV_SCSI_REQUEST_H_ + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "HyperVSCSIProtocol.h" + + +class HyperVSCSIRequest : public DoublyLinkedListLinkImpl { +public: + HyperVSCSIRequest(); + ~HyperVSCSIRequest(); + status_t InitCheck() const { return fStatus; } + + void Reset(); + status_t PrepareData(); + void Complete(uint8 status); + + bigtime_t GetTimeout() const { return fTimeout; } + void SetTimeout(bigtime_t timeout) { fTimeout = timeout; } + + void SetCCB(scsi_ccb* ccb) { fCCB = ccb; } + scsi_ccb* GetCCB() { return fCCB; } + hv_scsi_msg* GetMessage() { return &fMessage; } + uint32 GetMessageStatus() { return fMessage.header.status; } + void SetMessageType(uint32 type) { fMessage.header.type = type; } + void SetMessageData(hv_scsi_msg* message); + + vmbus_gpa_range* GetGPARange() { return fGPARange; } + uint32 GetGPARangeLength() { return fGPARangeLength; } + + void AddWaiter(ConditionVariableEntry* entry); + void Notify(); + +private: + status_t _FillGPARange(const physical_entry* sgList, uint32 sgCount, + uint32 dataLength); + +private: + mutex fLock; + status_t fStatus; + bigtime_t fTimeout; + scsi_ccb* fCCB; + + area_id fBounceArea; + void* fBounceBuffer; + phys_addr_t fBounceBufferPhys; + bool fBounceBufferInUse; + + vmbus_gpa_range* fGPARange; + uint32 fGPARangeLength; + hv_scsi_msg fMessage; + ConditionVariable fConditionVariable; +}; +typedef DoublyLinkedList HyperVSCSIRequestList; + + +#endif diff --git a/src/add-ons/kernel/busses/scsi/hyperv/Jamfile b/src/add-ons/kernel/busses/scsi/hyperv/Jamfile new file mode 100644 index 0000000000..29307d5914 --- /dev/null +++ b/src/add-ons/kernel/busses/scsi/hyperv/Jamfile @@ -0,0 +1,10 @@ +SubDir HAIKU_TOP src add-ons kernel busses scsi hyperv ; + +UsePrivateHeaders drivers hyperv ; +UsePrivateKernelHeaders ; + +KernelAddon hyperv_scsi : + HyperVSCSI.cpp + HyperVSCSIModule.cpp + HyperVSCSIRequest.cpp +; diff --git a/src/system/kernel/device_manager/device_manager.cpp b/src/system/kernel/device_manager/device_manager.cpp index a44d0aafb8..4616200dda 100644 --- a/src/system/kernel/device_manager/device_manager.cpp +++ b/src/system/kernel/device_manager/device_manager.cpp @@ -1678,7 +1678,7 @@ device_node::_GetNextDriverPath(void*& cookie, KPath& _path) } const char* bus; if (get_attr_string(this, B_DEVICE_BUS, &bus, false) == B_OK) { - if (strcmp(bus, "virtio") == 0) + if (strcmp(bus, "virtio") == 0 || strcmp(bus, "hyperv") == 0) _AddPath(*stack, "busses/scsi"); } _AddPath(*stack, "drivers", sGenericContextPath);