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:
John Davis
2026-03-21 11:20:30 +00:00
committed by Jérôme Duval
parent d6a2ec35b9
commit 17a4eb1ef9
12 changed files with 1540 additions and 3 deletions
+2
View File
@@ -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
+2
View File
@@ -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
+2 -2
View File
@@ -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"
+1
View File
@@ -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);