hyperv: Add SCSI controller support
Adds support for the Hyper-V SCSI controller as part of the hyperv_scsi driver. This driver enables the use of Hyper-V generation 2 VMs as the SCSI controller is the only storage method supported. IDE hard disks can be supported in the future on generation 1 VMs by this driver, but this will require a mechansim to prevent the ATA bus manager from handling hard disks and conflicting with this one. Change-Id: Iabb9c0471578caf1ef80754f39a09e1ce0eb4249 Reviewed-on: https://review.haiku-os.org/c/haiku/+/10504 Reviewed-by: Jérôme Duval <[email protected]> Tested-by: Commit checker robot <[email protected]>
This commit is contained in:
@@ -53,6 +53,7 @@ AddFilesToPackage add-ons kernel busses random :
|
|||||||
;
|
;
|
||||||
AddFilesToPackage add-ons kernel busses scsi :
|
AddFilesToPackage add-ons kernel busses scsi :
|
||||||
ahci
|
ahci
|
||||||
|
hyperv_scsi@x86,x86_64
|
||||||
virtio_scsi
|
virtio_scsi
|
||||||
;
|
;
|
||||||
AddFilesToPackage add-ons kernel busses usb :
|
AddFilesToPackage add-ons kernel busses usb :
|
||||||
@@ -326,6 +327,7 @@ AddBootModuleSymlinksToPackage
|
|||||||
efi_gpt
|
efi_gpt
|
||||||
generic_ide_pci
|
generic_ide_pci
|
||||||
hyperv@x86,x86_64
|
hyperv@x86,x86_64
|
||||||
|
hyperv_scsi@x86,x86_64
|
||||||
ide_isa@x86
|
ide_isa@x86
|
||||||
isa@x86,x86_64
|
isa@x86,x86_64
|
||||||
intel
|
intel
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ AddFilesToPackage add-ons kernel busses pci :
|
|||||||
;
|
;
|
||||||
AddFilesToPackage add-ons kernel busses scsi :
|
AddFilesToPackage add-ons kernel busses scsi :
|
||||||
ahci
|
ahci
|
||||||
|
hyperv_scsi@x86,x86_64
|
||||||
virtio_scsi
|
virtio_scsi
|
||||||
;
|
;
|
||||||
AddFilesToPackage add-ons kernel busses usb :
|
AddFilesToPackage add-ons kernel busses usb :
|
||||||
@@ -323,6 +324,7 @@ AddBootModuleSymlinksToPackage
|
|||||||
virtio_block
|
virtio_block
|
||||||
virtio_scsi
|
virtio_scsi
|
||||||
hyperv@x86,x86_64
|
hyperv@x86,x86_64
|
||||||
|
hyperv_scsi@x86,x86_64
|
||||||
efi_gpt
|
efi_gpt
|
||||||
intel
|
intel
|
||||||
bfs
|
bfs
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
#define HYPERV_PRETTYNAME_FIBRECHANNEL "Hyper-V Fibre Channel"
|
#define HYPERV_PRETTYNAME_FIBRECHANNEL "Hyper-V Fibre Channel"
|
||||||
#define HYPERV_PRETTYNAME_FILECOPY "Hyper-V File Copy"
|
#define HYPERV_PRETTYNAME_FILECOPY "Hyper-V File Copy"
|
||||||
#define HYPERV_PRETTYNAME_HEARTBEAT "Hyper-V Heartbeat"
|
#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_INPUT "Hyper-V Input"
|
||||||
#define HYPERV_PRETTYNAME_KEYBOARD "Hyper-V Keyboard"
|
#define HYPERV_PRETTYNAME_KEYBOARD "Hyper-V Keyboard"
|
||||||
#define HYPERV_PRETTYNAME_KVP "Hyper-V Data Exchange"
|
#define HYPERV_PRETTYNAME_KVP "Hyper-V Data Exchange"
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
#define HYPERV_PRETTYNAME_RDCONTROL "Hyper-V Remote Desktop Control"
|
#define HYPERV_PRETTYNAME_RDCONTROL "Hyper-V Remote Desktop Control"
|
||||||
#define HYPERV_PRETTYNAME_RDMA "Hyper-V RDMA"
|
#define HYPERV_PRETTYNAME_RDMA "Hyper-V RDMA"
|
||||||
#define HYPERV_PRETTYNAME_RDVIRT "Hyper-V Remote Desktop Virtualization"
|
#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_SHUTDOWN "Hyper-V Guest Shutdown"
|
||||||
#define HYPERV_PRETTYNAME_TIMESYNC "Hyper-V Time Synchronization"
|
#define HYPERV_PRETTYNAME_TIMESYNC "Hyper-V Time Synchronization"
|
||||||
#define HYPERV_PRETTYNAME_VSS "Hyper-V Volume Shadow Copy"
|
#define HYPERV_PRETTYNAME_VSS "Hyper-V Volume Shadow Copy"
|
||||||
|
|||||||
@@ -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 ahci ;
|
||||||
SubInclude HAIKU_TOP src add-ons kernel busses scsi 53c8xx ;
|
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 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 usb ;
|
||||||
SubInclude HAIKU_TOP src add-ons kernel busses scsi virtio ;
|
SubInclude HAIKU_TOP src add-ons kernel busses scsi virtio ;
|
||||||
|
|||||||
@@ -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<uint8>(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<uint8*>(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<HyperVSCSI*>(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<vmbus_pkt_header*>(fPacket);
|
||||||
|
hv_scsi_msg* message = reinterpret_cast<hv_scsi_msg*>(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<HyperVSCSI*>(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;
|
||||||
|
}
|
||||||
@@ -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 <new>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <AutoDeleter.h>
|
||||||
|
#include <AutoDeleterDrivers.h>
|
||||||
|
#include <bus/SCSI.h>
|
||||||
|
#include <condition_variable.h>
|
||||||
|
#include <lock.h>
|
||||||
|
#include <scsi_cmds.h>
|
||||||
|
#include <util/AutoLock.h>
|
||||||
|
|
||||||
|
#include <hyperv.h>
|
||||||
|
|
||||||
|
#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
|
||||||
@@ -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<HyperVSCSI*>(driverCookie);
|
||||||
|
delete scsi;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
hyperv_scsi_set_scsi_bus(scsi_sim_cookie cookie, scsi_bus bus)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
HyperVSCSI* scsi = reinterpret_cast<HyperVSCSI*>(cookie);
|
||||||
|
scsi->SetBus(bus);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
hyperv_scsi_scsi_io(scsi_sim_cookie cookie, scsi_ccb* ccb)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
HyperVSCSI* scsi = reinterpret_cast<HyperVSCSI*>(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<HyperVSCSI*>(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<HyperVSCSI*>(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<HyperVSCSI*>(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<device_node*>(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<uint32>(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
|
||||||
|
};
|
||||||
@@ -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 <hyperv_spec.h>
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
@@ -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 <vm/vm.h>
|
||||||
|
|
||||||
|
|
||||||
|
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<uint8*>(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<uint8*>(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;
|
||||||
|
}
|
||||||
@@ -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 <new>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <bus/SCSI.h>
|
||||||
|
#include <condition_variable.h>
|
||||||
|
#include <lock.h>
|
||||||
|
#include <scsi_cmds.h>
|
||||||
|
#include <util/DoublyLinkedList.h>
|
||||||
|
|
||||||
|
#include "HyperVSCSIProtocol.h"
|
||||||
|
|
||||||
|
|
||||||
|
class HyperVSCSIRequest : public DoublyLinkedListLinkImpl<HyperVSCSIRequest> {
|
||||||
|
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<HyperVSCSIRequest> HyperVSCSIRequestList;
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -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
|
||||||
|
;
|
||||||
@@ -1678,7 +1678,7 @@ device_node::_GetNextDriverPath(void*& cookie, KPath& _path)
|
|||||||
}
|
}
|
||||||
const char* bus;
|
const char* bus;
|
||||||
if (get_attr_string(this, B_DEVICE_BUS, &bus, false) == B_OK) {
|
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, "busses/scsi");
|
||||||
}
|
}
|
||||||
_AddPath(*stack, "drivers", sGenericContextPath);
|
_AddPath(*stack, "drivers", sGenericContextPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user