Virtio: added a driver with basic support for SCSI devices.

* Here is the Qemu command line option for Virtio SCSI devices:
-drive if=none,id=hd,file=haiku.image -device virtio-scsi-pci,id=scsi -device scsi-hd,drive=hd
* virtio_scsi.h is copied unchanged from FreeBSD, except for the _PACKED directive.
This commit is contained in:
Jérôme Duval
2013-07-02 22:44:51 +02:00
parent 46bfab031f
commit 82fda49e52
8 changed files with 1222 additions and 0 deletions
+1
View File
@@ -4,3 +4,4 @@ SubInclude HAIKU_TOP src add-ons kernel busses scsi ahci ;
SubInclude HAIKU_TOP src add-ons kernel busses scsi 53c8xx ;
SubInclude HAIKU_TOP src add-ons kernel busses scsi buslogic ;
SubInclude HAIKU_TOP src add-ons kernel busses scsi usb ;
SubInclude HAIKU_TOP src add-ons kernel busses scsi virtio ;
@@ -0,0 +1,11 @@
SubDir HAIKU_TOP src add-ons kernel busses scsi virtio ;
UsePrivateHeaders drivers virtio ;
UsePrivateKernelHeaders ;
KernelAddon virtio_scsi :
virtio_scsi.cpp
VirtioSCSIController.cpp
VirtioSCSIHelper.cpp
VirtioSCSIRequest.cpp
;
@@ -0,0 +1,264 @@
/*
* Copyright 2013, Jérôme Duval, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "VirtioSCSIPrivate.h"
#include <new>
#include <stdlib.h>
#include <string.h>
#include <util/AutoLock.h>
const char *
get_feature_name(uint32 feature)
{
switch (feature) {
case VIRTIO_SCSI_F_INOUT:
return "in out";
case VIRTIO_SCSI_F_HOTPLUG:
return "hotplug";
}
return NULL;
}
VirtioSCSIController::VirtioSCSIController(device_node *node)
:
fNode(node),
fVirtio(NULL),
fVirtioDevice(NULL),
fStatus(B_NO_INIT),
fRequest(NULL)
{
CALLED();
B_INITIALIZE_SPINLOCK(&fInterruptLock);
fInterruptCondition.Init(this, "virtio scsi transfer");
// get the Virtio device from our parent's parent
device_node *parent = gDeviceManager->get_parent_node(node);
device_node *virtioParent = gDeviceManager->get_parent_node(parent);
gDeviceManager->put_node(parent);
gDeviceManager->get_driver(virtioParent, (driver_module_info **)&fVirtio,
(void **)&fVirtioDevice);
gDeviceManager->put_node(virtioParent);
fVirtio->negociate_features(fVirtioDevice,
0 /*VIRTIO_SCSI_F_HOTPLUG*/,
&fFeatures, &get_feature_name);
fStatus = fVirtio->read_device_config(fVirtioDevice, 0, &fConfig,
sizeof(struct virtio_scsi_config));
if (fStatus != B_OK)
return;
fConfig.sense_size = VIRTIO_SCSI_SENSE_SIZE;
fConfig.cdb_size = VIRTIO_SCSI_CDB_SIZE;
fVirtio->write_device_config(fVirtioDevice,
offsetof(struct virtio_scsi_config, sense_size), &fConfig.sense_size,
sizeof(fConfig.sense_size));
fVirtio->write_device_config(fVirtioDevice,
offsetof(struct virtio_scsi_config, cdb_size), &fConfig.sense_size,
sizeof(fConfig.cdb_size));
fRequest = new(std::nothrow) VirtioSCSIRequest(true);
if (fRequest == NULL) {
fStatus = B_NO_MEMORY;
return;
}
::virtio_queue virtioQueues[3];
fStatus = fVirtio->alloc_queues(fVirtioDevice, 3, virtioQueues);
if (fStatus != B_OK) {
ERROR("queue allocation failed (%s)\n", strerror(fStatus));
return;
}
fControlVirtioQueue = virtioQueues[0];
fEventVirtioQueue = virtioQueues[1];
fRequestVirtioQueue = virtioQueues[2];
fStatus = fVirtio->setup_interrupt(fVirtioDevice, NULL, NULL);
if (fStatus != B_OK) {
ERROR("interrupt setup failed (%s)\n", strerror(fStatus));
return;
}
}
VirtioSCSIController::~VirtioSCSIController()
{
CALLED();
delete fRequest;
}
status_t
VirtioSCSIController::InitCheck()
{
return fStatus;
}
void
VirtioSCSIController::SetBus(scsi_bus bus)
{
fBus = bus;
}
void
VirtioSCSIController::PathInquiry(scsi_path_inquiry *info)
{
info->hba_inquiry = SCSI_PI_TAG_ABLE;
info->hba_misc = 0;
info->sim_priv = 0;
info->initiator_id = VIRTIO_SCSI_INITIATOR_ID;
info->hba_queue_size = fConfig.cmd_per_lun != 0 ? fConfig.cmd_per_lun : 1;
memset(info->vuhba_flags, 0, sizeof(info->vuhba_flags));
strlcpy(info->sim_vid, "Haiku", SCSI_SIM_ID);
strlcpy(info->hba_vid, "VirtIO", SCSI_HBA_ID);
strlcpy(info->sim_version, "1.0", SCSI_VERS);
strlcpy(info->hba_version, "1.0", SCSI_VERS);
strlcpy(info->controller_family, "Virtio", SCSI_FAM_ID);
strlcpy(info->controller_type, "Virtio", SCSI_TYPE_ID);
}
void
VirtioSCSIController::GetRestrictions(uint8 targetID, bool *isATAPI,
bool *noAutoSense, uint32 *maxBlocks)
{
*isATAPI = false;
*noAutoSense = true;
*maxBlocks = fConfig.cmd_per_lun;
}
uchar
VirtioSCSIController::ResetDevice(uchar targetID, uchar targetLUN)
{
return SCSI_REQ_CMP;
}
status_t
VirtioSCSIController::ExecuteRequest(scsi_ccb *ccb)
{
status_t result = fRequest->Start(ccb);
if (result != B_OK)
return result;
if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE && fRequest->HasSense()) {
TRACE("request sense\n");
fRequest->RequestSense();
fRequest->Finish(false);
return B_OK;
}
if (ccb->target_id > fConfig.max_target) {
ERROR("invalid target device\n");
fRequest->SetStatus(SCSI_TID_INVALID);
fRequest->Finish(false);
return B_BAD_INDEX;
}
if (ccb->target_lun > fConfig.max_lun) {
ERROR("invalid lun device\n");
fRequest->SetStatus(SCSI_LUN_INVALID);
fRequest->Finish(false);
return B_BAD_INDEX;
}
if (ccb->cdb_length > VIRTIO_SCSI_CDB_SIZE) {
fRequest->SetStatus(SCSI_REQ_INVALID);
fRequest->Finish(false);
return B_BAD_VALUE;
}
bool isOut = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT;
bool isIn = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN;
// TODO check feature inout if request is bidirectional
fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000
: VIRTIO_SCSI_STANDARD_TIMEOUT);
uint32 inCount = (isIn ? ccb->sg_count : 0) + 1;
uint32 outCount = (isOut ? ccb->sg_count : 0) + 1;
physical_entry entries[inCount + outCount];
fRequest->FillRequest(inCount, outCount, entries);
{
InterruptsSpinLocker locker(fInterruptLock);
fExpectsInterrupt = true;
fInterruptCondition.Add(&fInterruptConditionEntry);
}
fVirtio->queue_request_v(fRequestVirtioQueue, entries,
outCount, inCount, VirtioSCSIController::RequestCallback, this);
result = fInterruptConditionEntry.Wait(B_RELATIVE_TIMEOUT,
fRequest->Timeout());
{
InterruptsSpinLocker locker(fInterruptLock);
fExpectsInterrupt = false;
}
if (result != B_OK)
return result;
return fRequest->Finish(false);
}
uchar
VirtioSCSIController::AbortRequest(scsi_ccb *request)
{
return SCSI_REQ_CMP;
}
uchar
VirtioSCSIController::TerminateRequest(scsi_ccb *request)
{
return SCSI_REQ_CMP;
}
status_t
VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer,
size_t length)
{
CALLED();
return B_DEV_INVALID_IOCTL;
}
void
VirtioSCSIController::RequestCallback(void* cookie)
{
CALLED();
VirtioSCSIController* controller = (VirtioSCSIController*)cookie;
controller->_Interrupt();
}
void
VirtioSCSIController::_Interrupt()
{
SpinLocker locker(fInterruptLock);
fInterruptCondition.NotifyAll();
}
@@ -0,0 +1,79 @@
/*
* Copyright 2009, Michael Lotz, [email protected].
* Copyright 2004-2007, Axel Dörfler, [email protected].
* Copyright 2002/03, Thomas Kurschel. All rights reserved.
*
* Distributed under the terms of the MIT License.
*/
#include "VirtioSCSIPrivate.h"
#include <string.h>
#include <vm/vm.h>
/*! Copy data between ccb data and buffer
ccb - ccb to copy data from/to
offset - offset of data in ccb
allocation_length- limit of ccb's data buffer according to CDB
buffer - data to copy data from/to
size - number of bytes to copy
to_buffer - true: copy from ccb to buffer
false: copy from buffer to ccb
return: true, if data of ccb was large enough
*/
bool
copy_sg_data(scsi_ccb *ccb, uint offset, uint allocationLength,
void *buffer, int size, bool toBuffer)
{
const physical_entry *sgList = ccb->sg_list;
int sgCount = ccb->sg_count;
// skip unused S/G entries
while (sgCount > 0 && offset >= sgList->size) {
offset -= sgList->size;
++sgList;
--sgCount;
}
if (sgCount == 0)
return false;
// remaining bytes we are allowed to copy from/to ccb
int requestSize = MIN(allocationLength, ccb->data_length) - offset;
// copy one S/G entry at a time
for (; size > 0 && requestSize > 0 && sgCount > 0; ++sgList, --sgCount) {
size_t bytes;
bytes = MIN(size, requestSize);
bytes = MIN(bytes, sgList->size);
if (toBuffer) {
vm_memcpy_from_physical(buffer, sgList->address + offset, bytes,
false);
} else {
vm_memcpy_to_physical(sgList->address + offset, buffer, bytes,
false);
}
buffer = (char *)buffer + bytes;
size -= bytes;
offset = 0;
}
return size == 0;
}
void
swap_words(void *data, size_t size)
{
uint16 *word = (uint16 *)data;
size_t count = size / 2;
while (count--) {
*word = B_BENDIAN_TO_HOST_INT16(*word);
word++;
}
}
@@ -0,0 +1,148 @@
/*
* Copyright 2013, Jérôme Duval, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef VIRTIO_SCSI_PRIVATE_H
#define VIRTIO_SCSI_PRIVATE_H
#include <bus/SCSI.h>
#include <condition_variable.h>
#include <lock.h>
#include <scsi_cmds.h>
#include <virtio.h>
#include "virtio_scsi.h"
//#define TRACE_VIRTIO_SCSI
#ifdef TRACE_VIRTIO_SCSI
# define TRACE(x...) dprintf("virtio_scsi: " x)
#else
# define TRACE(x...) ;
#endif
#define ERROR(x...) dprintf("\33[33mvirtio_scsi:\33[0m " x)
#define CALLED() TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
extern device_manager_info* gDeviceManager;
extern scsi_for_sim_interface *gSCSI;
bool copy_sg_data(scsi_ccb *ccb, uint offset, uint allocationLength,
void *buffer, int size, bool toBuffer);
void swap_words(void *data, size_t size);
#define VIRTIO_SCSI_STANDARD_TIMEOUT 10 * 1000 * 1000
#define VIRTIO_SCSI_INITIATOR_ID 7
class VirtioSCSIRequest;
class VirtioSCSIController {
public:
VirtioSCSIController(device_node* node);
~VirtioSCSIController();
status_t InitCheck();
void SetBus(scsi_bus bus);
scsi_bus Bus() const { return fBus; }
void PathInquiry(scsi_path_inquiry* info);
void GetRestrictions(uint8 targetID, bool* isATAPI,
bool* noAutoSense, uint32* maxBlocks);
uchar ResetDevice(uchar targetID, uchar targetLUN);
status_t ExecuteRequest(scsi_ccb* request);
uchar AbortRequest(scsi_ccb* request);
uchar TerminateRequest(scsi_ccb* request);
status_t Control(uint8 targetID, uint32 op,
void* buffer, size_t length);
private:
static void RequestCallback(void *cookie);
void _Interrupt();
device_node* fNode;
scsi_bus fBus;
virtio_device_interface* fVirtio;
virtio_device* fVirtioDevice;
status_t fStatus;
struct virtio_scsi_config fConfig;
uint32 fFeatures;
::virtio_queue fControlVirtioQueue;
::virtio_queue fEventVirtioQueue;
::virtio_queue fRequestVirtioQueue;
area_id fArea;
struct virtio_scsi_event* fEvents;
VirtioSCSIRequest* fRequest;
spinlock fInterruptLock;
ConditionVariable fInterruptCondition;
ConditionVariableEntry fInterruptConditionEntry;
bool fExpectsInterrupt;
};
class VirtioSCSIRequest {
public:
VirtioSCSIRequest(bool hasLock);
~VirtioSCSIRequest();
void SetStatus(uint8 status);
uint8 Status() const { return fStatus; }
void SetTimeout(bigtime_t timeout);
bigtime_t Timeout() const { return fTimeout; }
bool HasSense() {
return (fResponse->sense_len > 0); }
void SetIsWrite(bool isWrite);
bool IsWrite() const { return fIsWrite; }
void SetBytesLeft(uint32 bytesLeft);
size_t* BytesLeft() { return &fBytesLeft; }
bool HasData() const
{ return fCCB->data_length > 0; }
status_t Finish(bool resubmit);
// SCSI stuff
status_t Start(scsi_ccb *ccb);
scsi_ccb* CCB() { return fCCB; }
void RequestSense();
void FillRequest(uint32 inCount, uint32 outCount,
physical_entry *entries);
private:
void _FillSense(scsi_sense *sense);
uchar _ResponseStatus();
mutex fLock;
bool fHasLock;
uint8 fStatus;
bigtime_t fTimeout;
size_t fBytesLeft;
bool fIsWrite;
scsi_ccb* fCCB;
// virtio scsi
void* fBuffer;
struct virtio_scsi_cmd_req *fRequest;
struct virtio_scsi_cmd_resp *fResponse;
};
#endif // VIRTIO_SCSI_PRIVATE_H
@@ -0,0 +1,228 @@
/*
* Copyright 2013, Jérôme Duval, [email protected].
* Copyright 2009, Michael Lotz, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "VirtioSCSIPrivate.h"
#include <string.h>
VirtioSCSIRequest::VirtioSCSIRequest(bool hasLock)
:
fHasLock(hasLock),
fTimeout(0),
fBytesLeft(0),
fIsWrite(false),
fCCB(NULL)
{
if (hasLock)
mutex_init(&fLock, "virtio scsi request");
fBuffer = malloc(sizeof(struct virtio_scsi_cmd_req)
+ sizeof(struct virtio_scsi_cmd_resp));
bzero(fBuffer, sizeof(struct virtio_scsi_cmd_req)
+ sizeof(struct virtio_scsi_cmd_resp));
fRequest = (struct virtio_scsi_cmd_req *)fBuffer;
fResponse = (struct virtio_scsi_cmd_resp *)
((addr_t)fBuffer + sizeof(struct virtio_scsi_cmd_req));
fResponse->sense_len = 0;
}
VirtioSCSIRequest::~VirtioSCSIRequest()
{
if (fHasLock)
mutex_destroy(&fLock);
free(fBuffer);
}
void
VirtioSCSIRequest::SetStatus(uint8 status)
{
fStatus = status;
}
void
VirtioSCSIRequest::SetTimeout(bigtime_t timeout)
{
fTimeout = timeout;
}
void
VirtioSCSIRequest::SetIsWrite(bool isWrite)
{
fIsWrite = isWrite;
}
void
VirtioSCSIRequest::SetBytesLeft(uint32 bytesLeft)
{
fBytesLeft = bytesLeft;
}
status_t
VirtioSCSIRequest::Start(scsi_ccb *ccb)
{
CALLED();
if (mutex_trylock(&fLock) != B_OK)
return B_BUSY;
fCCB = ccb;
fStatus = SCSI_REQ_CMP;
fCCB->device_status = SCSI_STATUS_GOOD;
fIsWrite = false;
bzero(fResponse, sizeof(struct virtio_scsi_cmd_resp));
TRACE("VirtioSCSIRequest::Start() opcode %x tid %x lun %x\n", ccb->cdb[0],
ccb->target_id, ccb->target_lun);
return B_OK;
}
status_t
VirtioSCSIRequest::Finish(bool resubmit)
{
CALLED();
fStatus = _ResponseStatus();
fCCB->data_resid = fResponse->resid;
fCCB->subsys_status = fStatus;
TRACE("VirtioSCSIRequest::Finish() status 0x%x response 0x%x resid:0x%x"
" sense_len:%x\n", fResponse->status, fResponse->response,
fResponse->resid, fResponse->sense_len);
if (fCCB->cdb[0] == SCSI_OP_INQUIRY) {
// when the request is an inquiry, don't do anything
} else if (fStatus == SCSI_REQ_CMP && fResponse->status != 0
&& HasSense()) {
// when the request completed and has set sense
// data, report this to the scsi stack by setting
// CHECK CONDITION status
TRACE("setting check condition\n");
fCCB->subsys_status = SCSI_REQ_CMP_ERR;
fCCB->device_status = SCSI_STATUS_CHECK_CONDITION;
// copy sense data if caller requested it
if ((fCCB->flags & SCSI_DIS_AUTOSENSE) == 0) {
size_t senseLength = min_c(sizeof(fCCB->sense),
fResponse->sense_len);
memcpy(fCCB->sense, fResponse->sense, senseLength);
fCCB->sense_resid = sizeof(fCCB->sense) - senseLength;
fCCB->subsys_status |= SCSI_AUTOSNS_VALID;
}
}
mutex_unlock(&fLock);
if (resubmit)
gSCSI->resubmit(fCCB);
else
gSCSI->finished(fCCB, 1);
TRACE("VirtioSCSIRequest::Finish() done\n");
return B_OK;
}
void
VirtioSCSIRequest::RequestSense()
{
CALLED();
// Copy sense data from last request into data buffer of current request.
// The sense data of last request is still present in the current request,
// as it isn't cleared on SCSI_OP_REQUEST_SENSE.
scsi_cmd_request_sense *command = (scsi_cmd_request_sense *)fCCB->cdb;
copy_sg_data(fCCB, 0, command->allocation_length, fResponse->sense,
fResponse->sense_len, false);
fCCB->data_resid = fCCB->data_length - min_c(min_c(fResponse->sense_len,
command->allocation_length), fCCB->data_length);
fResponse->sense_len = 0;
}
void
VirtioSCSIRequest::FillRequest(uint32 inCount, uint32 outCount,
physical_entry *entries)
{
CALLED();
fRequest->task_attr = VIRTIO_SCSI_S_SIMPLE;
fRequest->tag = (addr_t)fCCB;
fRequest->lun[0] = 1;
fRequest->lun[1] = fCCB->target_id;
fRequest->lun[2] = 0x40 | ((fCCB->target_lun >> 8) & 0x3f);
fRequest->lun[3] = (fCCB->target_lun >> 8) & 0xff;
memcpy(fRequest->cdb, fCCB->cdb, min_c(fCCB->cdb_length,
sizeof(fRequest->cdb)));
get_memory_map(fBuffer, sizeof(struct virtio_scsi_cmd_req)
+ sizeof(struct virtio_scsi_cmd_resp), &entries[0], 1);
entries[0].size = sizeof(struct virtio_scsi_cmd_req);
if (outCount > 1) {
memcpy(entries + 1, fCCB->sg_list, fCCB->sg_count
* sizeof(physical_entry));
}
entries[outCount].address = entries[0].address
+ sizeof(struct virtio_scsi_cmd_req);
entries[outCount].size = sizeof(struct virtio_scsi_cmd_resp);
if (inCount > 1) {
memcpy(entries + outCount + 1, fCCB->sg_list, fCCB->sg_count
* sizeof(physical_entry));
}
}
uchar
VirtioSCSIRequest::_ResponseStatus()
{
uchar status;
switch (fResponse->response) {
case VIRTIO_SCSI_S_OK:
status = SCSI_REQ_CMP;
break;
case VIRTIO_SCSI_S_OVERRUN:
status = SCSI_DATA_RUN_ERR;
break;
case VIRTIO_SCSI_S_ABORTED:
status = SCSI_REQ_ABORTED;
break;
case VIRTIO_SCSI_S_BAD_TARGET:
status = SCSI_TID_INVALID;
break;
case VIRTIO_SCSI_S_RESET:
status = SCSI_SCSI_BUS_RESET;
break;
case VIRTIO_SCSI_S_BUSY:
status = SCSI_SCSI_BUSY;
break;
case VIRTIO_SCSI_S_TRANSPORT_FAILURE:
case VIRTIO_SCSI_S_TARGET_FAILURE:
case VIRTIO_SCSI_S_NEXUS_FAILURE:
status = SCSI_NO_NEXUS;
break;
default: /* VIRTIO_SCSI_S_FAILURE */
status = SCSI_REQ_CMP_ERR;
break;
}
return status;
}
@@ -0,0 +1,339 @@
/*
* Copyright 2013, Jérôme Duval, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "VirtioSCSIPrivate.h"
#include <new>
#include <stdlib.h>
#include <string.h>
#define VIRTIO_SCSI_ID_GENERATOR "virtio_scsi/id"
#define VIRTIO_SCSI_ID_ITEM "virtio_scsi/id"
#define VIRTIO_SCSI_BRIDGE_PRETTY_NAME "Virtio SCSI Bridge"
#define VIRTIO_SCSI_CONTROLLER_PRETTY_NAME "Virtio SCSI Controller"
#define VIRTIO_SCSI_DEVICE_MODULE_NAME "busses/scsi/virtio_scsi/driver_v1"
#define VIRTIO_SCSI_SIM_MODULE_NAME "busses/scsi/virtio_scsi/sim/driver_v1"
device_manager_info *gDeviceManager;
scsi_for_sim_interface *gSCSI;
// #pragma mark - SIM module interface
static void
set_scsi_bus(scsi_sim_cookie cookie, scsi_bus bus)
{
VirtioSCSIController *sim = (VirtioSCSIController *)cookie;
sim->SetBus(bus);
}
static void
scsi_io(scsi_sim_cookie cookie, scsi_ccb *request)
{
CALLED();
VirtioSCSIController *sim = (VirtioSCSIController *)cookie;
if (sim->ExecuteRequest(request) == B_BUSY)
gSCSI->requeue(request, true);
}
static uchar
abort_io(scsi_sim_cookie cookie, scsi_ccb *request)
{
CALLED();
VirtioSCSIController *sim = (VirtioSCSIController *)cookie;
return sim->AbortRequest(request);
}
static uchar
reset_device(scsi_sim_cookie cookie, uchar targetID, uchar targetLUN)
{
CALLED();
VirtioSCSIController *sim = (VirtioSCSIController *)cookie;
return sim->ResetDevice(targetID, targetLUN);
}
static uchar
terminate_io(scsi_sim_cookie cookie, scsi_ccb *request)
{
CALLED();
VirtioSCSIController *sim = (VirtioSCSIController *)cookie;
return sim->TerminateRequest(request);
}
static uchar
path_inquiry(scsi_sim_cookie cookie, scsi_path_inquiry *info)
{
CALLED();
VirtioSCSIController *sim = (VirtioSCSIController *)cookie;
if (sim->Bus() == NULL)
return SCSI_NO_HBA;
sim->PathInquiry(info);
return SCSI_REQ_CMP;
}
//! this is called immediately before the SCSI bus manager scans the bus
static uchar
scan_bus(scsi_sim_cookie cookie)
{
CALLED();
return SCSI_REQ_CMP;
}
static uchar
reset_bus(scsi_sim_cookie cookie)
{
CALLED();
return SCSI_REQ_CMP;
}
/*! Get restrictions of one device
(used for non-SCSI transport protocols and bug fixes)
*/
static void
get_restrictions(scsi_sim_cookie cookie, uchar targetID, bool *isATAPI,
bool *noAutoSense, uint32 *maxBlocks)
{
CALLED();
VirtioSCSIController *sim = (VirtioSCSIController *)cookie;
sim->GetRestrictions(targetID, isATAPI, noAutoSense, maxBlocks);
}
static status_t
ioctl(scsi_sim_cookie cookie, uint8 targetID, uint32 op, void *buffer,
size_t length)
{
CALLED();
VirtioSCSIController *sim = (VirtioSCSIController *)cookie;
return sim->Control(targetID, op, buffer, length);
}
// #pragma mark -
static status_t
sim_init_bus(device_node *node, void **_cookie)
{
CALLED();
VirtioSCSIController *controller = new(std::nothrow)
VirtioSCSIController(node);
if (controller == NULL)
return B_NO_MEMORY;
status_t status = controller->InitCheck();
if (status < B_OK) {
delete controller;
return status;
}
*_cookie = controller;
return B_OK;
}
static void
sim_uninit_bus(void *cookie)
{
CALLED();
VirtioSCSIController *controller = (VirtioSCSIController*)cookie;
delete controller;
}
// #pragma mark -
static float
virtio_scsi_supports_device(device_node *parent)
{
const char *bus;
uint16 deviceType;
// make sure parent is really the Virtio bus manager
if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
return -1;
if (strcmp(bus, "virtio"))
return 0.0;
// check whether it's really a Virtio SCSI Device
if (gDeviceManager->get_attr_uint16(parent, VIRTIO_DEVICE_TYPE_ITEM,
&deviceType, true) != B_OK || deviceType != VIRTIO_DEVICE_ID_SCSI)
return 0.0;
TRACE("Virtio SCSI device found!\n");
return 0.6f;
}
static status_t
virtio_scsi_register_device(device_node *parent)
{
CALLED();
virtio_device_interface* virtio = NULL;
virtio_device* virtioDevice = NULL;
struct virtio_scsi_config config;
gDeviceManager->get_driver(parent, (driver_module_info **)&virtio,
(void **)&virtioDevice);
status_t status = virtio->read_device_config(virtioDevice, 0, &config,
sizeof(struct virtio_scsi_config));
if (status != B_OK)
return status;
uint32 max_targets = config.max_target + 1;
uint32 max_blocks = 0x10000;
if (config.max_sectors != 0)
max_blocks = config.max_sectors;
device_attr attrs[] = {
{ SCSI_DEVICE_MAX_TARGET_COUNT, B_UINT32_TYPE,
{ ui32: max_targets }},
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
{ string: VIRTIO_SCSI_BRIDGE_PRETTY_NAME }},
// DMA properties
{ B_DMA_MAX_SEGMENT_BLOCKS, B_UINT32_TYPE, { ui32: max_blocks }},
{ B_DMA_MAX_SEGMENT_COUNT, B_UINT32_TYPE,
{ ui32: config.seg_max }},
{ NULL }
};
return gDeviceManager->register_node(parent, VIRTIO_SCSI_DEVICE_MODULE_NAME,
attrs, NULL, NULL);
}
static status_t
virtio_scsi_init_driver(device_node *node, void **_cookie)
{
CALLED();
*_cookie = node;
return B_OK;
}
static status_t
virtio_scsi_register_child_devices(void *cookie)
{
CALLED();
device_node *node = (device_node *)cookie;
int32 id = gDeviceManager->create_id(VIRTIO_SCSI_ID_GENERATOR);
if (id < 0)
return id;
device_attr attrs[] = {
{ B_DEVICE_FIXED_CHILD, B_STRING_TYPE,
{ string: SCSI_FOR_SIM_MODULE_NAME }},
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
{ string: VIRTIO_SCSI_CONTROLLER_PRETTY_NAME }},
{ SCSI_DESCRIPTION_CONTROLLER_NAME, B_STRING_TYPE,
{ string: VIRTIO_SCSI_DEVICE_MODULE_NAME }},
{ B_DMA_MAX_TRANSFER_BLOCKS, B_UINT32_TYPE, { ui32: 255 }},
{ VIRTIO_SCSI_ID_ITEM, B_UINT32_TYPE, { ui32: (uint32)id }},
{ NULL }
};
status_t status = gDeviceManager->register_node(node,
VIRTIO_SCSI_SIM_MODULE_NAME, attrs, NULL, NULL);
if (status < B_OK)
gDeviceManager->free_id(VIRTIO_SCSI_ID_GENERATOR, id);
return status;
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
default:
return B_ERROR;
}
}
static scsi_sim_interface sVirtioSCSISimInterface = {
{
{
VIRTIO_SCSI_SIM_MODULE_NAME,
0,
std_ops
},
NULL, // supported devices
NULL, // register node
sim_init_bus,
sim_uninit_bus,
NULL, // register child devices
NULL, // rescan
NULL // bus_removed
},
set_scsi_bus,
scsi_io,
abort_io,
reset_device,
terminate_io,
path_inquiry,
scan_bus,
reset_bus,
get_restrictions,
ioctl
};
static driver_module_info sVirtioSCSIDevice = {
{
VIRTIO_SCSI_DEVICE_MODULE_NAME,
0,
std_ops
},
virtio_scsi_supports_device,
virtio_scsi_register_device,
virtio_scsi_init_driver,
NULL, // uninit_driver,
virtio_scsi_register_child_devices,
NULL, // rescan
NULL, // device_removed
};
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 *)&sVirtioSCSIDevice,
(module_info *)&sVirtioSCSISimInterface,
NULL
};
@@ -0,0 +1,152 @@
/*-
* This header is BSD licensed so anyone can use the definitions to implement
* compatible drivers/servers.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _VIRTIO_SCSI_H
#define _VIRTIO_SCSI_H
/* Feature bits */
#define VIRTIO_SCSI_F_INOUT 0x0001 /* Single request can contain both
* read and write buffers */
#define VIRTIO_SCSI_F_HOTPLUG 0x0002 /* Host should enable hot plug/unplug
* of new LUNs and targets.
*/
#define VIRTIO_SCSI_CDB_SIZE 32
#define VIRTIO_SCSI_SENSE_SIZE 96
/* SCSI command request, followed by data-out */
struct virtio_scsi_cmd_req {
uint8_t lun[8]; /* Logical Unit Number */
uint64_t tag; /* Command identifier */
uint8_t task_attr; /* Task attribute */
uint8_t prio;
uint8_t crn;
uint8_t cdb[VIRTIO_SCSI_CDB_SIZE];
} _PACKED;
/* Response, followed by sense data and data-in */
struct virtio_scsi_cmd_resp {
uint32_t sense_len; /* Sense data length */
uint32_t resid; /* Residual bytes in data buffer */
uint16_t status_qualifier; /* Status qualifier */
uint8_t status; /* Command completion status */
uint8_t response; /* Response values */
uint8_t sense[VIRTIO_SCSI_SENSE_SIZE];
} _PACKED;
/* Task Management Request */
struct virtio_scsi_ctrl_tmf_req {
uint32_t type;
uint32_t subtype;
uint8_t lun[8];
uint64_t tag;
} _PACKED;
struct virtio_scsi_ctrl_tmf_resp {
uint8_t response;
} _PACKED;
/* Asynchronous notification query/subscription */
struct virtio_scsi_ctrl_an_req {
uint32_t type;
uint8_t lun[8];
uint32_t event_requested;
} _PACKED;
struct virtio_scsi_ctrl_an_resp {
uint32_t event_actual;
uint8_t response;
} _PACKED;
struct virtio_scsi_event {
uint32_t event;
uint8_t lun[8];
uint32_t reason;
} _PACKED;
struct virtio_scsi_config {
uint32_t num_queues;
uint32_t seg_max;
uint32_t max_sectors;
uint32_t cmd_per_lun;
uint32_t event_info_size;
uint32_t sense_size;
uint32_t cdb_size;
uint16_t max_channel;
uint16_t max_target;
uint32_t max_lun;
} _PACKED;
/* Response codes */
#define VIRTIO_SCSI_S_OK 0
#define VIRTIO_SCSI_S_FUNCTION_COMPLETE 0
#define VIRTIO_SCSI_S_OVERRUN 1
#define VIRTIO_SCSI_S_ABORTED 2
#define VIRTIO_SCSI_S_BAD_TARGET 3
#define VIRTIO_SCSI_S_RESET 4
#define VIRTIO_SCSI_S_BUSY 5
#define VIRTIO_SCSI_S_TRANSPORT_FAILURE 6
#define VIRTIO_SCSI_S_TARGET_FAILURE 7
#define VIRTIO_SCSI_S_NEXUS_FAILURE 8
#define VIRTIO_SCSI_S_FAILURE 9
#define VIRTIO_SCSI_S_FUNCTION_SUCCEEDED 10
#define VIRTIO_SCSI_S_FUNCTION_REJECTED 11
#define VIRTIO_SCSI_S_INCORRECT_LUN 12
/* Controlq type codes. */
#define VIRTIO_SCSI_T_TMF 0
#define VIRTIO_SCSI_T_AN_QUERY 1
#define VIRTIO_SCSI_T_AN_SUBSCRIBE 2
/* Valid TMF subtypes. */
#define VIRTIO_SCSI_T_TMF_ABORT_TASK 0
#define VIRTIO_SCSI_T_TMF_ABORT_TASK_SET 1
#define VIRTIO_SCSI_T_TMF_CLEAR_ACA 2
#define VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET 3
#define VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET 4
#define VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET 5
#define VIRTIO_SCSI_T_TMF_QUERY_TASK 6
#define VIRTIO_SCSI_T_TMF_QUERY_TASK_SET 7
/* Events. */
#define VIRTIO_SCSI_T_EVENTS_MISSED 0x80000000
#define VIRTIO_SCSI_T_NO_EVENT 0
#define VIRTIO_SCSI_T_TRANSPORT_RESET 1
#define VIRTIO_SCSI_T_ASYNC_NOTIFY 2
/* Reasons of transport reset event */
#define VIRTIO_SCSI_EVT_RESET_HARD 0
#define VIRTIO_SCSI_EVT_RESET_RESCAN 1
#define VIRTIO_SCSI_EVT_RESET_REMOVED 2
#define VIRTIO_SCSI_S_SIMPLE 0
#define VIRTIO_SCSI_S_ORDERED 1
#define VIRTIO_SCSI_S_HEAD 2
#define VIRTIO_SCSI_S_ACA 3
#endif /* _VIRTIO_SCSI_H */