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(uint64 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 fCurrentRequest(0), 41 fEventDPC(NULL) 42 { 43 CALLED(); 44 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 uint16 requestedSizes[3] = { 0, 0, 0 }; 86 requestedSizes[2] = fConfig.seg_max + 2; 87 // two entries are taken up by the header and result 88 89 fStatus = fVirtio->alloc_queues(fVirtioDevice, 3, virtioQueues, 90 requestedSizes); 91 if (fStatus != B_OK) { 92 ERROR("queue allocation failed (%s)\n", strerror(fStatus)); 93 return; 94 } 95 96 fControlVirtioQueue = virtioQueues[0]; 97 fEventVirtioQueue = virtioQueues[1]; 98 fRequestVirtioQueue = virtioQueues[2]; 99 100 for (uint32 i = 0; i < VIRTIO_SCSI_NUM_EVENTS; i++) 101 _SubmitEvent(i); 102 103 fStatus = fVirtio->setup_interrupt(fVirtioDevice, NULL, this); 104 if (fStatus != B_OK) { 105 ERROR("interrupt setup failed (%s)\n", strerror(fStatus)); 106 return; 107 } 108 109 fStatus = fVirtio->queue_setup_interrupt(fControlVirtioQueue, 110 NULL, NULL); 111 if (fStatus == B_OK) { 112 fStatus = fVirtio->queue_setup_interrupt(fEventVirtioQueue, 113 VirtioSCSIController::_EventCallback, this); 114 } 115 if (fStatus == B_OK) { 116 fStatus = fVirtio->queue_setup_interrupt(fRequestVirtioQueue, 117 VirtioSCSIController::_RequestCallback, this); 118 } 119 120 if (fStatus != B_OK) { 121 ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus)); 122 return; 123 } 124 } 125 126 127 VirtioSCSIController::~VirtioSCSIController() 128 { 129 CALLED(); 130 delete fRequest; 131 132 gSCSI->free_dpc(fEventDPC); 133 } 134 135 136 status_t 137 VirtioSCSIController::InitCheck() 138 { 139 return fStatus; 140 } 141 142 143 void 144 VirtioSCSIController::SetBus(scsi_bus bus) 145 { 146 fBus = bus; 147 } 148 149 150 void 151 VirtioSCSIController::PathInquiry(scsi_path_inquiry *info) 152 { 153 info->hba_inquiry = SCSI_PI_TAG_ABLE; 154 info->hba_misc = 0; 155 info->initiator_id = VIRTIO_SCSI_INITIATOR_ID; 156 info->hba_queue_size = fConfig.cmd_per_lun != 0 ? fConfig.cmd_per_lun : 1; 157 memset(info->vuhba_flags, 0, sizeof(info->vuhba_flags)); 158 159 strlcpy(info->sim_vid, "Haiku", SCSI_SIM_ID); 160 strlcpy(info->hba_vid, "VirtIO", SCSI_HBA_ID); 161 162 strlcpy(info->sim_version, "1.0", SCSI_VERS); 163 strlcpy(info->hba_version, "1.0", SCSI_VERS); 164 strlcpy(info->controller_family, "Virtio", SCSI_FAM_ID); 165 strlcpy(info->controller_type, "Virtio", SCSI_TYPE_ID); 166 } 167 168 169 void 170 VirtioSCSIController::GetRestrictions(uint8 targetID, bool *isATAPI, 171 bool *noAutoSense, uint32 *maxBlocks) 172 { 173 *isATAPI = false; 174 *noAutoSense = true; 175 *maxBlocks = fConfig.cmd_per_lun; 176 } 177 178 179 uchar 180 VirtioSCSIController::ResetDevice(uchar targetID, uchar targetLUN) 181 { 182 return SCSI_REQ_CMP; 183 } 184 185 186 status_t 187 VirtioSCSIController::ExecuteRequest(scsi_ccb *ccb) 188 { 189 status_t result = fRequest->Start(ccb); 190 if (result != B_OK) 191 return result; 192 193 if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE && fRequest->HasSense()) { 194 TRACE("request sense\n"); 195 fRequest->RequestSense(); 196 fRequest->Finish(false); 197 return B_OK; 198 } 199 200 if (ccb->target_id > fConfig.max_target) { 201 ERROR("invalid target device\n"); 202 fRequest->SetStatus(SCSI_TID_INVALID); 203 fRequest->Finish(false); 204 return B_BAD_INDEX; 205 } 206 207 if (ccb->target_lun > fConfig.max_lun) { 208 ERROR("invalid lun device\n"); 209 fRequest->SetStatus(SCSI_LUN_INVALID); 210 fRequest->Finish(false); 211 return B_BAD_INDEX; 212 } 213 214 if (ccb->cdb_length > VIRTIO_SCSI_CDB_SIZE) { 215 fRequest->SetStatus(SCSI_REQ_INVALID); 216 fRequest->Finish(false); 217 return B_BAD_VALUE; 218 } 219 220 bool isOut = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT; 221 bool isIn = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN; 222 223 // TODO check feature inout if request is bidirectional 224 225 fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000 226 : VIRTIO_SCSI_STANDARD_TIMEOUT); 227 228 uint32 inCount = (isIn ? ccb->sg_count : 0) + 1; 229 uint32 outCount = (isOut ? ccb->sg_count : 0) + 1; 230 BStackOrHeapArray<physical_entry, 16> entries(inCount + outCount); 231 if (!entries.IsValid()) { 232 fRequest->SetStatus(SCSI_REQ_INVALID); 233 fRequest->Finish(false); 234 return B_NO_MEMORY; 235 } 236 fRequest->FillRequest(inCount, outCount, entries); 237 238 atomic_add(&fCurrentRequest, 1); 239 ConditionVariableEntry entry; 240 fInterruptCondition.Add(&entry); 241 242 result = fVirtio->queue_request_v(fRequestVirtioQueue, entries, 243 outCount, inCount, (void *)(addr_t)fCurrentRequest); 244 245 if (result != B_OK) 246 ERROR("queueing failed with status: %#" B_PRIx32 "\n", result); 247 else { 248 result = entry.Wait(B_RELATIVE_TIMEOUT, fRequest->Timeout()); 249 if (result != B_OK) 250 ERROR("wait failed with status: %#" B_PRIx32 "\n", result); 251 } 252 253 if (result != B_OK) { 254 fRequest->Abort(); 255 return result; 256 } 257 258 return fRequest->Finish(false); 259 } 260 261 262 uchar 263 VirtioSCSIController::AbortRequest(scsi_ccb *request) 264 { 265 return SCSI_REQ_CMP; 266 } 267 268 269 uchar 270 VirtioSCSIController::TerminateRequest(scsi_ccb *request) 271 { 272 return SCSI_REQ_CMP; 273 } 274 275 276 status_t 277 VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer, 278 size_t length) 279 { 280 CALLED(); 281 return B_DEV_INVALID_IOCTL; 282 } 283 284 285 void 286 VirtioSCSIController::_RequestCallback(void* driverCookie, void* cookie) 287 { 288 CALLED(); 289 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 290 controller->_RequestInterrupt(); 291 } 292 293 294 void 295 VirtioSCSIController::_RequestInterrupt() 296 { 297 void* cookie = NULL; 298 while (fVirtio->queue_dequeue(fRequestVirtioQueue, &cookie, NULL)) { 299 if ((int32)(addr_t)cookie == atomic_get(&fCurrentRequest)) 300 fInterruptCondition.NotifyAll(); 301 } 302 } 303 304 305 306 void 307 VirtioSCSIController::_EventCallback(void* driverCookie, void* cookie) 308 { 309 CALLED(); 310 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 311 312 virtio_scsi_event* event = NULL; 313 while (controller->fVirtio->queue_dequeue(controller->fEventVirtioQueue, 314 (void**)&event, NULL)) { 315 controller->_EventInterrupt(event); 316 } 317 } 318 319 320 void 321 VirtioSCSIController::_EventInterrupt(struct virtio_scsi_event* event) 322 { 323 CALLED(); 324 TRACE("events %#x\n", event->event); 325 if ((event->event & VIRTIO_SCSI_T_EVENTS_MISSED) != 0) { 326 ERROR("events missed\n"); 327 } else switch (event->event) { 328 case VIRTIO_SCSI_T_TRANSPORT_RESET: 329 ERROR("transport reset\n"); 330 break; 331 case VIRTIO_SCSI_T_ASYNC_NOTIFY: 332 ERROR("async notify\n"); 333 break; 334 case VIRTIO_SCSI_T_PARAM_CHANGE: 335 { 336 uint16 sense = (event->reason >> 8) 337 | ((event->reason & 0xff) << 8); 338 if (sense == SCSIS_ASC_CAPACITY_DATA_HAS_CHANGED) { 339 ERROR("capacity data has changed for %x:%x\n", event->lun[1], 340 event->lun[2] << 8 | event->lun[3]); 341 gSCSI->schedule_dpc(fBus, fEventDPC, _RescanChildBus, this); 342 } else 343 ERROR("param change, unknown reason\n"); 344 break; 345 } 346 default: 347 ERROR("unknown event %#x\n", event->event); 348 break; 349 } 350 } 351 352 353 void 354 VirtioSCSIController::_SubmitEvent(uint32 eventNumber) 355 { 356 CALLED(); 357 struct virtio_scsi_event* event = &fEventBuffers[eventNumber]; 358 bzero(event, sizeof(struct virtio_scsi_event)); 359 360 physical_entry entry; 361 get_memory_map(event, sizeof(struct virtio_scsi_event), &entry, 1); 362 363 fVirtio->queue_request_v(fEventVirtioQueue, &entry, 364 0, 1, event); 365 } 366 367 368 void 369 VirtioSCSIController::_RescanChildBus(void *cookie) 370 { 371 CALLED(); 372 VirtioSCSIController* controller = (VirtioSCSIController*)cookie; 373 device_node *childNode = NULL; 374 const device_attr attrs[] = { { NULL } }; 375 if (gDeviceManager->get_next_child_node(controller->fNode, attrs, 376 &childNode) != B_OK) { 377 ERROR("couldn't find the child node for %p\n", controller->fNode); 378 return; 379 } 380 381 gDeviceManager->rescan_node(childNode); 382 TRACE("rescan done %p\n", childNode); 383 gDeviceManager->put_node(childNode); 384 } 385