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->sim_priv = 0; 156 info->initiator_id = VIRTIO_SCSI_INITIATOR_ID; 157 info->hba_queue_size = fConfig.cmd_per_lun != 0 ? fConfig.cmd_per_lun : 1; 158 memset(info->vuhba_flags, 0, sizeof(info->vuhba_flags)); 159 160 strlcpy(info->sim_vid, "Haiku", SCSI_SIM_ID); 161 strlcpy(info->hba_vid, "VirtIO", SCSI_HBA_ID); 162 163 strlcpy(info->sim_version, "1.0", SCSI_VERS); 164 strlcpy(info->hba_version, "1.0", SCSI_VERS); 165 strlcpy(info->controller_family, "Virtio", SCSI_FAM_ID); 166 strlcpy(info->controller_type, "Virtio", SCSI_TYPE_ID); 167 } 168 169 170 void 171 VirtioSCSIController::GetRestrictions(uint8 targetID, bool *isATAPI, 172 bool *noAutoSense, uint32 *maxBlocks) 173 { 174 *isATAPI = false; 175 *noAutoSense = true; 176 *maxBlocks = fConfig.cmd_per_lun; 177 } 178 179 180 uchar 181 VirtioSCSIController::ResetDevice(uchar targetID, uchar targetLUN) 182 { 183 return SCSI_REQ_CMP; 184 } 185 186 187 status_t 188 VirtioSCSIController::ExecuteRequest(scsi_ccb *ccb) 189 { 190 status_t result = fRequest->Start(ccb); 191 if (result != B_OK) 192 return result; 193 194 if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE && fRequest->HasSense()) { 195 TRACE("request sense\n"); 196 fRequest->RequestSense(); 197 fRequest->Finish(false); 198 return B_OK; 199 } 200 201 if (ccb->target_id > fConfig.max_target) { 202 ERROR("invalid target device\n"); 203 fRequest->SetStatus(SCSI_TID_INVALID); 204 fRequest->Finish(false); 205 return B_BAD_INDEX; 206 } 207 208 if (ccb->target_lun > fConfig.max_lun) { 209 ERROR("invalid lun device\n"); 210 fRequest->SetStatus(SCSI_LUN_INVALID); 211 fRequest->Finish(false); 212 return B_BAD_INDEX; 213 } 214 215 if (ccb->cdb_length > VIRTIO_SCSI_CDB_SIZE) { 216 fRequest->SetStatus(SCSI_REQ_INVALID); 217 fRequest->Finish(false); 218 return B_BAD_VALUE; 219 } 220 221 bool isOut = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT; 222 bool isIn = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN; 223 224 // TODO check feature inout if request is bidirectional 225 226 fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000 227 : VIRTIO_SCSI_STANDARD_TIMEOUT); 228 229 uint32 inCount = (isIn ? ccb->sg_count : 0) + 1; 230 uint32 outCount = (isOut ? ccb->sg_count : 0) + 1; 231 BStackOrHeapArray<physical_entry, 16> entries(inCount + outCount); 232 if (!entries.IsValid()) { 233 fRequest->SetStatus(SCSI_REQ_INVALID); 234 fRequest->Finish(false); 235 return B_NO_MEMORY; 236 } 237 fRequest->FillRequest(inCount, outCount, entries); 238 239 atomic_add(&fCurrentRequest, 1); 240 ConditionVariableEntry entry; 241 fInterruptCondition.Add(&entry); 242 243 result = fVirtio->queue_request_v(fRequestVirtioQueue, entries, 244 outCount, inCount, (void *)(addr_t)fCurrentRequest); 245 246 if (result != B_OK) 247 ERROR("queueing failed with status: %#" B_PRIx32 "\n", result); 248 else { 249 result = entry.Wait(B_RELATIVE_TIMEOUT, fRequest->Timeout()); 250 if (result != B_OK) 251 ERROR("wait failed with status: %#" B_PRIx32 "\n", result); 252 } 253 254 if (result != B_OK) { 255 fRequest->Abort(); 256 return result; 257 } 258 259 return fRequest->Finish(false); 260 } 261 262 263 uchar 264 VirtioSCSIController::AbortRequest(scsi_ccb *request) 265 { 266 return SCSI_REQ_CMP; 267 } 268 269 270 uchar 271 VirtioSCSIController::TerminateRequest(scsi_ccb *request) 272 { 273 return SCSI_REQ_CMP; 274 } 275 276 277 status_t 278 VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer, 279 size_t length) 280 { 281 CALLED(); 282 return B_DEV_INVALID_IOCTL; 283 } 284 285 286 void 287 VirtioSCSIController::_RequestCallback(void* driverCookie, void* cookie) 288 { 289 CALLED(); 290 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 291 controller->_RequestInterrupt(); 292 } 293 294 295 void 296 VirtioSCSIController::_RequestInterrupt() 297 { 298 void* cookie = NULL; 299 while (fVirtio->queue_dequeue(fRequestVirtioQueue, &cookie, NULL)) { 300 if ((int32)(addr_t)cookie == atomic_get(&fCurrentRequest)) 301 fInterruptCondition.NotifyAll(); 302 } 303 } 304 305 306 307 void 308 VirtioSCSIController::_EventCallback(void* driverCookie, void* cookie) 309 { 310 CALLED(); 311 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 312 313 virtio_scsi_event* event = NULL; 314 while (controller->fVirtio->queue_dequeue(controller->fEventVirtioQueue, 315 (void**)&event, NULL)) { 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