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 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 atomic_add(&fCurrentRequest, 1); 235 fInterruptCondition.Add(&fInterruptConditionEntry); 236 237 fVirtio->queue_request_v(fRequestVirtioQueue, entries, 238 outCount, inCount, (void *)(addr_t)fCurrentRequest); 239 240 result = fInterruptConditionEntry.Wait(B_RELATIVE_TIMEOUT, 241 fRequest->Timeout()); 242 243 if (result != B_OK) { 244 ERROR("wait failed with status: %#" B_PRIx32 "\n", result); 245 fRequest->Abort(); 246 return result; 247 } 248 249 return fRequest->Finish(false); 250 } 251 252 253 uchar 254 VirtioSCSIController::AbortRequest(scsi_ccb *request) 255 { 256 return SCSI_REQ_CMP; 257 } 258 259 260 uchar 261 VirtioSCSIController::TerminateRequest(scsi_ccb *request) 262 { 263 return SCSI_REQ_CMP; 264 } 265 266 267 status_t 268 VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer, 269 size_t length) 270 { 271 CALLED(); 272 return B_DEV_INVALID_IOCTL; 273 } 274 275 276 void 277 VirtioSCSIController::_RequestCallback(void* driverCookie, void* cookie) 278 { 279 CALLED(); 280 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 281 controller->_RequestInterrupt(); 282 } 283 284 285 void 286 VirtioSCSIController::_RequestInterrupt() 287 { 288 void* cookie = NULL; 289 while (fVirtio->queue_dequeue(fRequestVirtioQueue, &cookie, NULL)) { 290 if ((int32)(addr_t)cookie == atomic_get(&fCurrentRequest)) 291 fInterruptCondition.NotifyAll(); 292 } 293 } 294 295 296 297 void 298 VirtioSCSIController::_EventCallback(void* driverCookie, void* cookie) 299 { 300 CALLED(); 301 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 302 303 virtio_scsi_event* event = NULL; 304 while (controller->fVirtio->queue_dequeue(controller->fEventVirtioQueue, 305 (void**)&event, NULL)) { 306 controller->_EventInterrupt(event); 307 } 308 } 309 310 311 void 312 VirtioSCSIController::_EventInterrupt(struct virtio_scsi_event* event) 313 { 314 CALLED(); 315 TRACE("events %#x\n", event->event); 316 if ((event->event & VIRTIO_SCSI_T_EVENTS_MISSED) != 0) { 317 ERROR("events missed\n"); 318 } else switch (event->event) { 319 case VIRTIO_SCSI_T_TRANSPORT_RESET: 320 ERROR("transport reset\n"); 321 break; 322 case VIRTIO_SCSI_T_ASYNC_NOTIFY: 323 ERROR("async notify\n"); 324 break; 325 case VIRTIO_SCSI_T_PARAM_CHANGE: 326 { 327 uint16 sense = (event->reason >> 8) 328 | ((event->reason & 0xff) << 8); 329 if (sense == SCSIS_ASC_CAPACITY_DATA_HAS_CHANGED) { 330 ERROR("capacity data has changed for %x:%x\n", event->lun[1], 331 event->lun[2] << 8 | event->lun[3]); 332 gSCSI->schedule_dpc(fBus, fEventDPC, _RescanChildBus, this); 333 } else 334 ERROR("param change, unknown reason\n"); 335 break; 336 } 337 default: 338 ERROR("unknown event %#x\n", event->event); 339 break; 340 } 341 } 342 343 344 void 345 VirtioSCSIController::_SubmitEvent(uint32 eventNumber) 346 { 347 CALLED(); 348 struct virtio_scsi_event* event = &fEventBuffers[eventNumber]; 349 bzero(event, sizeof(struct virtio_scsi_event)); 350 351 physical_entry entry; 352 get_memory_map(event, sizeof(struct virtio_scsi_event), &entry, 1); 353 354 fVirtio->queue_request_v(fEventVirtioQueue, &entry, 355 0, 1, event); 356 } 357 358 359 void 360 VirtioSCSIController::_RescanChildBus(void *cookie) 361 { 362 CALLED(); 363 VirtioSCSIController* controller = (VirtioSCSIController*)cookie; 364 device_node *childNode = NULL; 365 const device_attr attrs[] = { { NULL } }; 366 if (gDeviceManager->get_next_child_node(controller->fNode, attrs, 367 &childNode) != B_OK) { 368 ERROR("couldn't find the child node for %p\n", controller->fNode); 369 return; 370 } 371 372 gDeviceManager->rescan_node(childNode); 373 TRACE("rescan done %p\n", childNode); 374 gDeviceManager->put_node(childNode); 375 } 376