1 /* 2 * Copyright 2013, Jérôme Duval, korli@users.berlios.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "VirtioSCSIPrivate.h" 8 9 #include <StackOrHeapArray.h> 10 11 #include <new> 12 #include <stdlib.h> 13 #include <strings.h> 14 15 #include <util/AutoLock.h> 16 17 18 const char * 19 get_feature_name(uint32 feature) 20 { 21 switch (feature) { 22 case VIRTIO_SCSI_F_INOUT: 23 return "in out"; 24 case VIRTIO_SCSI_F_HOTPLUG: 25 return "hotplug"; 26 case VIRTIO_SCSI_F_CHANGE: 27 return "change"; 28 } 29 return NULL; 30 } 31 32 33 VirtioSCSIController::VirtioSCSIController(device_node *node) 34 : 35 fNode(node), 36 fVirtio(NULL), 37 fVirtioDevice(NULL), 38 fStatus(B_NO_INIT), 39 fRequest(NULL), 40 fEventDPC(NULL) 41 { 42 CALLED(); 43 44 B_INITIALIZE_SPINLOCK(&fInterruptLock); 45 fInterruptCondition.Init(this, "virtio scsi transfer"); 46 47 if (gSCSI->alloc_dpc(&fEventDPC) != B_OK) 48 return; 49 50 // get the Virtio device from our parent's parent 51 device_node *parent = gDeviceManager->get_parent_node(node); 52 device_node *virtioParent = gDeviceManager->get_parent_node(parent); 53 gDeviceManager->put_node(parent); 54 55 gDeviceManager->get_driver(virtioParent, (driver_module_info **)&fVirtio, 56 (void **)&fVirtioDevice); 57 gDeviceManager->put_node(virtioParent); 58 59 fVirtio->negotiate_features(fVirtioDevice, 60 VIRTIO_SCSI_F_CHANGE /*VIRTIO_SCSI_F_HOTPLUG*/, 61 &fFeatures, &get_feature_name); 62 63 fStatus = fVirtio->read_device_config(fVirtioDevice, 0, &fConfig, 64 sizeof(struct virtio_scsi_config)); 65 if (fStatus != B_OK) 66 return; 67 68 fConfig.sense_size = VIRTIO_SCSI_SENSE_SIZE; 69 fConfig.cdb_size = VIRTIO_SCSI_CDB_SIZE; 70 71 fVirtio->write_device_config(fVirtioDevice, 72 offsetof(struct virtio_scsi_config, sense_size), &fConfig.sense_size, 73 sizeof(fConfig.sense_size)); 74 fVirtio->write_device_config(fVirtioDevice, 75 offsetof(struct virtio_scsi_config, cdb_size), &fConfig.cdb_size, 76 sizeof(fConfig.cdb_size)); 77 78 fRequest = new(std::nothrow) VirtioSCSIRequest(true); 79 if (fRequest == NULL) { 80 fStatus = B_NO_MEMORY; 81 return; 82 } 83 84 ::virtio_queue virtioQueues[3]; 85 fStatus = fVirtio->alloc_queues(fVirtioDevice, 3, virtioQueues); 86 if (fStatus != B_OK) { 87 ERROR("queue allocation failed (%s)\n", strerror(fStatus)); 88 return; 89 } 90 91 fControlVirtioQueue = virtioQueues[0]; 92 fEventVirtioQueue = virtioQueues[1]; 93 fRequestVirtioQueue = virtioQueues[2]; 94 95 for (uint32 i = 0; i < VIRTIO_SCSI_NUM_EVENTS; i++) 96 _SubmitEvent(i); 97 98 fStatus = fVirtio->setup_interrupt(fVirtioDevice, NULL, this); 99 if (fStatus != B_OK) { 100 ERROR("interrupt setup failed (%s)\n", strerror(fStatus)); 101 return; 102 } 103 104 fStatus = fVirtio->queue_setup_interrupt(fControlVirtioQueue, 105 NULL, NULL); 106 if (fStatus == B_OK) { 107 fStatus = fVirtio->queue_setup_interrupt(fEventVirtioQueue, 108 VirtioSCSIController::_EventCallback, this); 109 } 110 if (fStatus == B_OK) { 111 fStatus = fVirtio->queue_setup_interrupt(fRequestVirtioQueue, 112 VirtioSCSIController::_RequestCallback, this); 113 } 114 115 if (fStatus != B_OK) { 116 ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus)); 117 return; 118 } 119 } 120 121 122 VirtioSCSIController::~VirtioSCSIController() 123 { 124 CALLED(); 125 delete fRequest; 126 127 gSCSI->free_dpc(fEventDPC); 128 } 129 130 131 status_t 132 VirtioSCSIController::InitCheck() 133 { 134 return fStatus; 135 } 136 137 138 void 139 VirtioSCSIController::SetBus(scsi_bus bus) 140 { 141 fBus = bus; 142 } 143 144 145 void 146 VirtioSCSIController::PathInquiry(scsi_path_inquiry *info) 147 { 148 info->hba_inquiry = SCSI_PI_TAG_ABLE; 149 info->hba_misc = 0; 150 info->sim_priv = 0; 151 info->initiator_id = VIRTIO_SCSI_INITIATOR_ID; 152 info->hba_queue_size = fConfig.cmd_per_lun != 0 ? fConfig.cmd_per_lun : 1; 153 memset(info->vuhba_flags, 0, sizeof(info->vuhba_flags)); 154 155 strlcpy(info->sim_vid, "Haiku", SCSI_SIM_ID); 156 strlcpy(info->hba_vid, "VirtIO", SCSI_HBA_ID); 157 158 strlcpy(info->sim_version, "1.0", SCSI_VERS); 159 strlcpy(info->hba_version, "1.0", SCSI_VERS); 160 strlcpy(info->controller_family, "Virtio", SCSI_FAM_ID); 161 strlcpy(info->controller_type, "Virtio", SCSI_TYPE_ID); 162 } 163 164 165 void 166 VirtioSCSIController::GetRestrictions(uint8 targetID, bool *isATAPI, 167 bool *noAutoSense, uint32 *maxBlocks) 168 { 169 *isATAPI = false; 170 *noAutoSense = true; 171 *maxBlocks = fConfig.cmd_per_lun; 172 } 173 174 175 uchar 176 VirtioSCSIController::ResetDevice(uchar targetID, uchar targetLUN) 177 { 178 return SCSI_REQ_CMP; 179 } 180 181 182 status_t 183 VirtioSCSIController::ExecuteRequest(scsi_ccb *ccb) 184 { 185 status_t result = fRequest->Start(ccb); 186 if (result != B_OK) 187 return result; 188 189 if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE && fRequest->HasSense()) { 190 TRACE("request sense\n"); 191 fRequest->RequestSense(); 192 fRequest->Finish(false); 193 return B_OK; 194 } 195 196 if (ccb->target_id > fConfig.max_target) { 197 ERROR("invalid target device\n"); 198 fRequest->SetStatus(SCSI_TID_INVALID); 199 fRequest->Finish(false); 200 return B_BAD_INDEX; 201 } 202 203 if (ccb->target_lun > fConfig.max_lun) { 204 ERROR("invalid lun device\n"); 205 fRequest->SetStatus(SCSI_LUN_INVALID); 206 fRequest->Finish(false); 207 return B_BAD_INDEX; 208 } 209 210 if (ccb->cdb_length > VIRTIO_SCSI_CDB_SIZE) { 211 fRequest->SetStatus(SCSI_REQ_INVALID); 212 fRequest->Finish(false); 213 return B_BAD_VALUE; 214 } 215 216 bool isOut = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT; 217 bool isIn = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN; 218 219 // TODO check feature inout if request is bidirectional 220 221 fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000 222 : VIRTIO_SCSI_STANDARD_TIMEOUT); 223 224 uint32 inCount = (isIn ? ccb->sg_count : 0) + 1; 225 uint32 outCount = (isOut ? ccb->sg_count : 0) + 1; 226 BStackOrHeapArray<physical_entry, 16> entries(inCount + outCount); 227 if (!entries.IsValid()) { 228 fRequest->SetStatus(SCSI_REQ_INVALID); 229 fRequest->Finish(false); 230 return B_NO_MEMORY; 231 } 232 fRequest->FillRequest(inCount, outCount, entries); 233 234 { 235 InterruptsSpinLocker locker(fInterruptLock); 236 fExpectsInterrupt = true; 237 fInterruptCondition.Add(&fInterruptConditionEntry); 238 } 239 240 fVirtio->queue_request_v(fRequestVirtioQueue, entries, 241 outCount, inCount, NULL); 242 243 result = fInterruptConditionEntry.Wait(B_RELATIVE_TIMEOUT, 244 fRequest->Timeout()); 245 246 { 247 InterruptsSpinLocker locker(fInterruptLock); 248 fExpectsInterrupt = false; 249 } 250 251 if (result != B_OK) 252 return result; 253 254 return fRequest->Finish(false); 255 } 256 257 258 uchar 259 VirtioSCSIController::AbortRequest(scsi_ccb *request) 260 { 261 return SCSI_REQ_CMP; 262 } 263 264 265 uchar 266 VirtioSCSIController::TerminateRequest(scsi_ccb *request) 267 { 268 return SCSI_REQ_CMP; 269 } 270 271 272 status_t 273 VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer, 274 size_t length) 275 { 276 CALLED(); 277 return B_DEV_INVALID_IOCTL; 278 } 279 280 281 void 282 VirtioSCSIController::_RequestCallback(void* driverCookie, void* cookie) 283 { 284 CALLED(); 285 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 286 287 while (controller->fVirtio->queue_dequeue( 288 controller->fRequestVirtioQueue, NULL) != NULL) { 289 } 290 291 controller->_RequestInterrupt(); 292 } 293 294 295 void 296 VirtioSCSIController::_RequestInterrupt() 297 { 298 SpinLocker locker(fInterruptLock); 299 fInterruptCondition.NotifyAll(); 300 } 301 302 303 304 void 305 VirtioSCSIController::_EventCallback(void* driverCookie, void* cookie) 306 { 307 CALLED(); 308 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 309 310 while (true) { 311 virtio_scsi_event* event = (virtio_scsi_event*) 312 controller->fVirtio->queue_dequeue(controller->fEventVirtioQueue, 313 NULL); 314 if (event == NULL) 315 break; 316 controller->_EventInterrupt(event); 317 } 318 } 319 320 321 void 322 VirtioSCSIController::_EventInterrupt(struct virtio_scsi_event* event) 323 { 324 CALLED(); 325 TRACE("events %#x\n", event->event); 326 if ((event->event & VIRTIO_SCSI_T_EVENTS_MISSED) != 0) { 327 ERROR("events missed\n"); 328 } else switch (event->event) { 329 case VIRTIO_SCSI_T_TRANSPORT_RESET: 330 ERROR("transport reset\n"); 331 break; 332 case VIRTIO_SCSI_T_ASYNC_NOTIFY: 333 ERROR("async notify\n"); 334 break; 335 case VIRTIO_SCSI_T_PARAM_CHANGE: 336 { 337 uint16 sense = (event->reason >> 8) 338 | ((event->reason & 0xff) << 8); 339 if (sense == SCSIS_ASC_CAPACITY_DATA_HAS_CHANGED) { 340 ERROR("capacity data has changed for %x:%x\n", event->lun[1], 341 event->lun[2] << 8 | event->lun[3]); 342 gSCSI->schedule_dpc(fBus, fEventDPC, _RescanChildBus, this); 343 } else 344 ERROR("param change, unknown reason\n"); 345 break; 346 } 347 default: 348 ERROR("unknown event %#x\n", event->event); 349 break; 350 } 351 } 352 353 354 void 355 VirtioSCSIController::_SubmitEvent(uint32 eventNumber) 356 { 357 CALLED(); 358 struct virtio_scsi_event* event = &fEventBuffers[eventNumber]; 359 bzero(event, sizeof(struct virtio_scsi_event)); 360 361 physical_entry entry; 362 get_memory_map(event, sizeof(struct virtio_scsi_event), &entry, 1); 363 364 fVirtio->queue_request_v(fEventVirtioQueue, &entry, 365 0, 1, event); 366 } 367 368 369 void 370 VirtioSCSIController::_RescanChildBus(void *cookie) 371 { 372 CALLED(); 373 VirtioSCSIController* controller = (VirtioSCSIController*)cookie; 374 device_node *childNode = NULL; 375 const device_attr attrs[] = { { NULL } }; 376 if (gDeviceManager->get_next_child_node(controller->fNode, attrs, 377 &childNode) != B_OK) { 378 ERROR("couldn't find the child node for %p\n", controller->fNode); 379 return; 380 } 381 382 gDeviceManager->rescan_node(childNode); 383 TRACE("rescan done %p\n", childNode); 384 gDeviceManager->put_node(childNode); 385 } 386